bevy_render/view/visibility/
mod.rs

1mod range;
2mod render_layers;
3
4use std::any::TypeId;
5
6pub use range::*;
7pub use render_layers::*;
8
9use bevy_app::{Plugin, PostUpdate};
10use bevy_asset::{Assets, Handle};
11use bevy_derive::Deref;
12use bevy_ecs::{prelude::*, query::QueryFilter};
13use bevy_hierarchy::{Children, Parent};
14use bevy_reflect::{std_traits::ReflectDefault, Reflect};
15use bevy_transform::{components::GlobalTransform, TransformSystem};
16use bevy_utils::{Parallel, TypeIdMap};
17
18use crate::{
19    camera::{Camera, CameraProjection},
20    mesh::Mesh,
21    primitives::{Aabb, Frustum, Sphere},
22};
23
24use super::NoCpuCulling;
25
26/// User indication of whether an entity is visible. Propagates down the entity hierarchy.
27///
28/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who
29/// are set to [`Inherited`](Self::Inherited) will also be hidden.
30///
31/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and
32/// `Visibility` to set the values of each entity's [`InheritedVisibility`] component.
33#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
34#[reflect(Component, Default)]
35pub enum Visibility {
36    /// An entity with `Visibility::Inherited` will inherit the Visibility of its [`Parent`].
37    ///
38    /// A root-level entity that is set to `Inherited` will be visible.
39    #[default]
40    Inherited,
41    /// An entity with `Visibility::Hidden` will be unconditionally hidden.
42    Hidden,
43    /// An entity with `Visibility::Visible` will be unconditionally visible.
44    ///
45    /// Note that an entity with `Visibility::Visible` will be visible regardless of whether the
46    /// [`Parent`] entity is hidden.
47    Visible,
48}
49
50// Allows `&Visibility == Visibility`
51impl PartialEq<Visibility> for &Visibility {
52    #[inline]
53    fn eq(&self, other: &Visibility) -> bool {
54        // Use the base Visibility == Visibility implementation.
55        <Visibility as PartialEq<Visibility>>::eq(*self, other)
56    }
57}
58
59// Allows `Visibility == &Visibility`
60impl PartialEq<&Visibility> for Visibility {
61    #[inline]
62    fn eq(&self, other: &&Visibility) -> bool {
63        // Use the base Visibility == Visibility implementation.
64        <Visibility as PartialEq<Visibility>>::eq(self, *other)
65    }
66}
67
68/// Whether or not an entity is visible in the hierarchy.
69/// This will not be accurate until [`VisibilityPropagate`] runs in the [`PostUpdate`] schedule.
70///
71/// If this is false, then [`ViewVisibility`] should also be false.
72///
73/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
74#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
75#[reflect(Component, Default)]
76pub struct InheritedVisibility(bool);
77
78impl InheritedVisibility {
79    /// An entity that is invisible in the hierarchy.
80    pub const HIDDEN: Self = Self(false);
81    /// An entity that is visible in the hierarchy.
82    pub const VISIBLE: Self = Self(true);
83
84    /// Returns `true` if the entity is visible in the hierarchy.
85    /// Otherwise, returns `false`.
86    #[inline]
87    pub fn get(self) -> bool {
88        self.0
89    }
90}
91
92/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
93///
94/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].
95/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].
96/// Because of this, values of this type will be marked as changed every frame, even when they do not change.
97///
98/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.
99///
100/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
101/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
102#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
103#[reflect(Component, Default)]
104pub struct ViewVisibility(bool);
105
106impl ViewVisibility {
107    /// An entity that cannot be seen from any views.
108    pub const HIDDEN: Self = Self(false);
109
110    /// Returns `true` if the entity is visible in any view.
111    /// Otherwise, returns `false`.
112    #[inline]
113    pub fn get(self) -> bool {
114        self.0
115    }
116
117    /// Sets the visibility to `true`. This should not be considered reversible for a given frame,
118    /// as this component tracks whether or not the entity visible in _any_ view.
119    ///
120    /// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set
121    /// to the proper value in [`CheckVisibility`].
122    ///
123    /// You should only manually set this if you are defining a custom visibility system,
124    /// in which case the system should be placed in the [`CheckVisibility`] set.
125    /// For normal user-defined entity visibility, see [`Visibility`].
126    ///
127    /// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
128    /// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
129    #[inline]
130    pub fn set(&mut self) {
131        self.0 = true;
132    }
133}
134
135/// A [`Bundle`] of the [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`]
136/// [`Component`]s, which describe the visibility of an entity.
137///
138/// * To show or hide an entity, you should set its [`Visibility`].
139/// * To get the inherited visibility of an entity, you should get its [`InheritedVisibility`].
140/// * For visibility hierarchies to work correctly, you must have both all of [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`].
141///   * You may use the [`VisibilityBundle`] to guarantee this.
142#[derive(Bundle, Debug, Clone, Default)]
143pub struct VisibilityBundle {
144    /// The visibility of the entity.
145    pub visibility: Visibility,
146    // The inherited visibility of the entity.
147    pub inherited_visibility: InheritedVisibility,
148    // The computed visibility of the entity.
149    pub view_visibility: ViewVisibility,
150}
151
152/// Use this component to opt-out of built-in frustum culling for entities, see
153/// [`Frustum`].
154///
155/// It can be used for example:
156/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations,
157/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`]
158/// to appear in the reflection of a [`Mesh`] within.
159#[derive(Component, Default, Reflect)]
160#[reflect(Component, Default)]
161pub struct NoFrustumCulling;
162
163/// Collection of entities visible from the current view.
164///
165/// This component contains all entities which are visible from the currently
166/// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`]
167/// system set, and renderers can use it to optimize rendering of a particular view, to
168/// prevent drawing items not visible from that view.
169///
170/// This component is intended to be attached to the same entity as the [`Camera`] and
171/// the [`Frustum`] defining the view.
172#[derive(Clone, Component, Default, Debug, Reflect)]
173#[reflect(Component, Default)]
174pub struct VisibleEntities {
175    #[reflect(ignore)]
176    pub entities: TypeIdMap<Vec<Entity>>,
177}
178
179impl VisibleEntities {
180    pub fn get<QF>(&self) -> &[Entity]
181    where
182        QF: 'static,
183    {
184        match self.entities.get(&TypeId::of::<QF>()) {
185            Some(entities) => &entities[..],
186            None => &[],
187        }
188    }
189
190    pub fn get_mut<QF>(&mut self) -> &mut Vec<Entity>
191    where
192        QF: 'static,
193    {
194        self.entities.entry(TypeId::of::<QF>()).or_default()
195    }
196
197    pub fn iter<QF>(&self) -> impl DoubleEndedIterator<Item = &Entity>
198    where
199        QF: 'static,
200    {
201        self.get::<QF>().iter()
202    }
203
204    pub fn len<QF>(&self) -> usize
205    where
206        QF: 'static,
207    {
208        self.get::<QF>().len()
209    }
210
211    pub fn is_empty<QF>(&self) -> bool
212    where
213        QF: 'static,
214    {
215        self.get::<QF>().is_empty()
216    }
217
218    pub fn clear<QF>(&mut self)
219    where
220        QF: 'static,
221    {
222        self.get_mut::<QF>().clear();
223    }
224
225    pub fn push<QF>(&mut self, entity: Entity)
226    where
227        QF: 'static,
228    {
229        self.get_mut::<QF>().push(entity);
230    }
231}
232
233/// A convenient alias for `With<Handle<Mesh>>`, for use with
234/// [`VisibleEntities`].
235pub type WithMesh = With<Handle<Mesh>>;
236
237#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
238pub enum VisibilitySystems {
239    /// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,
240    /// calculating and inserting an [`Aabb`] to relevant entities.
241    CalculateBounds,
242    /// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
243    UpdateFrusta,
244    /// Label for the system propagating the [`InheritedVisibility`] in a
245    /// [`hierarchy`](bevy_hierarchy).
246    VisibilityPropagate,
247    /// Label for the [`check_visibility`] system updating [`ViewVisibility`]
248    /// of each entity and the [`VisibleEntities`] of each view.
249    CheckVisibility,
250}
251
252pub struct VisibilityPlugin;
253
254impl Plugin for VisibilityPlugin {
255    fn build(&self, app: &mut bevy_app::App) {
256        use VisibilitySystems::*;
257
258        app.configure_sets(
259            PostUpdate,
260            (CalculateBounds, UpdateFrusta, VisibilityPropagate)
261                .before(CheckVisibility)
262                .after(TransformSystem::TransformPropagate),
263        )
264        .add_systems(
265            PostUpdate,
266            (
267                calculate_bounds.in_set(CalculateBounds),
268                (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate),
269                check_visibility::<WithMesh>.in_set(CheckVisibility),
270            ),
271        );
272    }
273}
274
275/// Computes and adds an [`Aabb`] component to entities with a
276/// [`Handle<Mesh>`](Mesh) component and without a [`NoFrustumCulling`] component.
277///
278/// This system is used in system set [`VisibilitySystems::CalculateBounds`].
279pub fn calculate_bounds(
280    mut commands: Commands,
281    meshes: Res<Assets<Mesh>>,
282    without_aabb: Query<(Entity, &Handle<Mesh>), (Without<Aabb>, Without<NoFrustumCulling>)>,
283) {
284    for (entity, mesh_handle) in &without_aabb {
285        if let Some(mesh) = meshes.get(mesh_handle) {
286            if let Some(aabb) = mesh.compute_aabb() {
287                commands.entity(entity).try_insert(aabb);
288            }
289        }
290    }
291}
292
293/// Updates [`Frustum`].
294///
295/// This system is used in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
296pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
297    mut views: Query<
298        (&GlobalTransform, &T, &mut Frustum),
299        Or<(Changed<GlobalTransform>, Changed<T>)>,
300    >,
301) {
302    for (transform, projection, mut frustum) in &mut views {
303        *frustum = projection.compute_frustum(transform);
304    }
305}
306
307fn visibility_propagate_system(
308    changed: Query<
309        (Entity, &Visibility, Option<&Parent>, Option<&Children>),
310        (With<InheritedVisibility>, Changed<Visibility>),
311    >,
312    mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
313    children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
314) {
315    for (entity, visibility, parent, children) in &changed {
316        let is_visible = match visibility {
317            Visibility::Visible => true,
318            Visibility::Hidden => false,
319            // fall back to true if no parent is found or parent lacks components
320            Visibility::Inherited => parent
321                .and_then(|p| visibility_query.get(p.get()).ok())
322                .map_or(true, |(_, x)| x.get()),
323        };
324        let (_, mut inherited_visibility) = visibility_query
325            .get_mut(entity)
326            .expect("With<InheritedVisibility> ensures this query will return a value");
327
328        // Only update the visibility if it has changed.
329        // This will also prevent the visibility from propagating multiple times in the same frame
330        // if this entity's visibility has been updated recursively by its parent.
331        if inherited_visibility.get() != is_visible {
332            inherited_visibility.0 = is_visible;
333
334            // Recursively update the visibility of each child.
335            for &child in children.into_iter().flatten() {
336                let _ =
337                    propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
338            }
339        }
340    }
341}
342
343fn propagate_recursive(
344    parent_is_visible: bool,
345    entity: Entity,
346    visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
347    children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
348    // BLOCKED: https://github.com/rust-lang/rust/issues/31436
349    // We use a result here to use the `?` operator. Ideally we'd use a try block instead
350) -> Result<(), ()> {
351    // Get the visibility components for the current entity.
352    // If the entity does not have the required components, just return early.
353    let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
354
355    let is_visible = match visibility {
356        Visibility::Visible => true,
357        Visibility::Hidden => false,
358        Visibility::Inherited => parent_is_visible,
359    };
360
361    // Only update the visibility if it has changed.
362    if inherited_visibility.get() != is_visible {
363        inherited_visibility.0 = is_visible;
364
365        // Recursively update the visibility of each child.
366        for &child in children_query.get(entity).ok().into_iter().flatten() {
367            let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
368        }
369    }
370
371    Ok(())
372}
373
374/// Resets the view visibility of every entity.
375/// Entities that are visible will be marked as such later this frame
376/// by a [`VisibilitySystems::CheckVisibility`] system.
377fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {
378    query.iter_mut().for_each(|mut view_visibility| {
379        // NOTE: We do not use `set_if_neq` here, as we don't care about
380        // change detection for view visibility, and adding a branch to every
381        // loop iteration would pessimize performance.
382        *view_visibility.bypass_change_detection() = ViewVisibility::HIDDEN;
383    });
384}
385
386/// System updating the visibility of entities each frame.
387///
388/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each
389/// frame, it updates the [`ViewVisibility`] of all entities, and for each view
390/// also compute the [`VisibleEntities`] for that view.
391///
392/// This system needs to be run for each type of renderable entity. If you add a
393/// new type of renderable entity, you'll need to add an instantiation of this
394/// system to the [`VisibilitySystems::CheckVisibility`] set so that Bevy will
395/// detect visibility properly for those entities.
396pub fn check_visibility<QF>(
397    mut thread_queues: Local<Parallel<Vec<Entity>>>,
398    mut view_query: Query<(
399        Entity,
400        &mut VisibleEntities,
401        &Frustum,
402        Option<&RenderLayers>,
403        &Camera,
404        Has<NoCpuCulling>,
405    )>,
406    mut visible_aabb_query: Query<
407        (
408            Entity,
409            &InheritedVisibility,
410            &mut ViewVisibility,
411            Option<&RenderLayers>,
412            Option<&Aabb>,
413            &GlobalTransform,
414            Has<NoFrustumCulling>,
415            Has<VisibilityRange>,
416        ),
417        QF,
418    >,
419    visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
420) where
421    QF: QueryFilter + 'static,
422{
423    let visible_entity_ranges = visible_entity_ranges.as_deref();
424
425    for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in
426        &mut view_query
427    {
428        if !camera.is_active {
429            continue;
430        }
431
432        let view_mask = maybe_view_mask.unwrap_or_default();
433
434        visible_aabb_query.par_iter_mut().for_each_init(
435            || thread_queues.borrow_local_mut(),
436            |queue, query_item| {
437                let (
438                    entity,
439                    inherited_visibility,
440                    mut view_visibility,
441                    maybe_entity_mask,
442                    maybe_model_aabb,
443                    transform,
444                    no_frustum_culling,
445                    has_visibility_range,
446                ) = query_item;
447
448                // Skip computing visibility for entities that are configured to be hidden.
449                // ViewVisibility has already been reset in `reset_view_visibility`.
450                if !inherited_visibility.get() {
451                    return;
452                }
453
454                let entity_mask = maybe_entity_mask.unwrap_or_default();
455                if !view_mask.intersects(entity_mask) {
456                    return;
457                }
458
459                // If outside of the visibility range, cull.
460                if has_visibility_range
461                    && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
462                        !visible_entity_ranges.entity_is_in_range_of_view(entity, view)
463                    })
464                {
465                    return;
466                }
467
468                // If we have an aabb, do frustum culling
469                if !no_frustum_culling && !no_cpu_culling {
470                    if let Some(model_aabb) = maybe_model_aabb {
471                        let world_from_local = transform.affine();
472                        let model_sphere = Sphere {
473                            center: world_from_local.transform_point3a(model_aabb.center),
474                            radius: transform.radius_vec3a(model_aabb.half_extents),
475                        };
476                        // Do quick sphere-based frustum culling
477                        if !frustum.intersects_sphere(&model_sphere, false) {
478                            return;
479                        }
480                        // Do aabb-based frustum culling
481                        if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
482                            return;
483                        }
484                    }
485                }
486
487                view_visibility.set();
488                queue.push(entity);
489            },
490        );
491
492        visible_entities.clear::<QF>();
493        thread_queues.drain_into(visible_entities.get_mut::<QF>());
494    }
495}
496
497#[cfg(test)]
498mod test {
499    use bevy_app::prelude::*;
500    use bevy_ecs::prelude::*;
501
502    use super::*;
503
504    use bevy_hierarchy::BuildWorldChildren;
505
506    fn visibility_bundle(visibility: Visibility) -> VisibilityBundle {
507        VisibilityBundle {
508            visibility,
509            ..Default::default()
510        }
511    }
512
513    #[test]
514    fn visibility_propagation() {
515        let mut app = App::new();
516        app.add_systems(Update, visibility_propagate_system);
517
518        let root1 = app
519            .world_mut()
520            .spawn(visibility_bundle(Visibility::Hidden))
521            .id();
522        let root1_child1 = app.world_mut().spawn(VisibilityBundle::default()).id();
523        let root1_child2 = app
524            .world_mut()
525            .spawn(visibility_bundle(Visibility::Hidden))
526            .id();
527        let root1_child1_grandchild1 = app.world_mut().spawn(VisibilityBundle::default()).id();
528        let root1_child2_grandchild1 = app.world_mut().spawn(VisibilityBundle::default()).id();
529
530        app.world_mut()
531            .entity_mut(root1)
532            .push_children(&[root1_child1, root1_child2]);
533        app.world_mut()
534            .entity_mut(root1_child1)
535            .push_children(&[root1_child1_grandchild1]);
536        app.world_mut()
537            .entity_mut(root1_child2)
538            .push_children(&[root1_child2_grandchild1]);
539
540        let root2 = app.world_mut().spawn(VisibilityBundle::default()).id();
541        let root2_child1 = app.world_mut().spawn(VisibilityBundle::default()).id();
542        let root2_child2 = app
543            .world_mut()
544            .spawn(visibility_bundle(Visibility::Hidden))
545            .id();
546        let root2_child1_grandchild1 = app.world_mut().spawn(VisibilityBundle::default()).id();
547        let root2_child2_grandchild1 = app.world_mut().spawn(VisibilityBundle::default()).id();
548
549        app.world_mut()
550            .entity_mut(root2)
551            .push_children(&[root2_child1, root2_child2]);
552        app.world_mut()
553            .entity_mut(root2_child1)
554            .push_children(&[root2_child1_grandchild1]);
555        app.world_mut()
556            .entity_mut(root2_child2)
557            .push_children(&[root2_child2_grandchild1]);
558
559        app.update();
560
561        let is_visible = |e: Entity| {
562            app.world()
563                .entity(e)
564                .get::<InheritedVisibility>()
565                .unwrap()
566                .get()
567        };
568        assert!(
569            !is_visible(root1),
570            "invisibility propagates down tree from root"
571        );
572        assert!(
573            !is_visible(root1_child1),
574            "invisibility propagates down tree from root"
575        );
576        assert!(
577            !is_visible(root1_child2),
578            "invisibility propagates down tree from root"
579        );
580        assert!(
581            !is_visible(root1_child1_grandchild1),
582            "invisibility propagates down tree from root"
583        );
584        assert!(
585            !is_visible(root1_child2_grandchild1),
586            "invisibility propagates down tree from root"
587        );
588
589        assert!(
590            is_visible(root2),
591            "visibility propagates down tree from root"
592        );
593        assert!(
594            is_visible(root2_child1),
595            "visibility propagates down tree from root"
596        );
597        assert!(
598            !is_visible(root2_child2),
599            "visibility propagates down tree from root, but local invisibility is preserved"
600        );
601        assert!(
602            is_visible(root2_child1_grandchild1),
603            "visibility propagates down tree from root"
604        );
605        assert!(
606            !is_visible(root2_child2_grandchild1),
607            "child's invisibility propagates down to grandchild"
608        );
609    }
610
611    #[test]
612    fn visibility_propagation_unconditional_visible() {
613        use Visibility::{Hidden, Inherited, Visible};
614
615        let mut app = App::new();
616        app.add_systems(Update, visibility_propagate_system);
617
618        let root1 = app.world_mut().spawn(visibility_bundle(Visible)).id();
619        let root1_child1 = app.world_mut().spawn(visibility_bundle(Inherited)).id();
620        let root1_child2 = app.world_mut().spawn(visibility_bundle(Hidden)).id();
621        let root1_child1_grandchild1 = app.world_mut().spawn(visibility_bundle(Visible)).id();
622        let root1_child2_grandchild1 = app.world_mut().spawn(visibility_bundle(Visible)).id();
623
624        let root2 = app.world_mut().spawn(visibility_bundle(Inherited)).id();
625        let root3 = app.world_mut().spawn(visibility_bundle(Hidden)).id();
626
627        app.world_mut()
628            .entity_mut(root1)
629            .push_children(&[root1_child1, root1_child2]);
630        app.world_mut()
631            .entity_mut(root1_child1)
632            .push_children(&[root1_child1_grandchild1]);
633        app.world_mut()
634            .entity_mut(root1_child2)
635            .push_children(&[root1_child2_grandchild1]);
636
637        app.update();
638
639        let is_visible = |e: Entity| {
640            app.world()
641                .entity(e)
642                .get::<InheritedVisibility>()
643                .unwrap()
644                .get()
645        };
646        assert!(
647            is_visible(root1),
648            "an unconditionally visible root is visible"
649        );
650        assert!(
651            is_visible(root1_child1),
652            "an inheriting child of an unconditionally visible parent is visible"
653        );
654        assert!(
655            !is_visible(root1_child2),
656            "a hidden child on an unconditionally visible parent is hidden"
657        );
658        assert!(
659            is_visible(root1_child1_grandchild1),
660            "an unconditionally visible child of an inheriting parent is visible"
661        );
662        assert!(
663            is_visible(root1_child2_grandchild1),
664            "an unconditionally visible child of a hidden parent is visible"
665        );
666        assert!(is_visible(root2), "an inheriting root is visible");
667        assert!(!is_visible(root3), "a hidden root is hidden");
668    }
669
670    #[test]
671    fn visibility_propagation_change_detection() {
672        let mut world = World::new();
673        let mut schedule = Schedule::default();
674        schedule.add_systems(visibility_propagate_system);
675
676        // Set up an entity hierarchy.
677
678        let id1 = world.spawn(VisibilityBundle::default()).id();
679
680        let id2 = world.spawn(VisibilityBundle::default()).id();
681        world.entity_mut(id1).push_children(&[id2]);
682
683        let id3 = world.spawn(visibility_bundle(Visibility::Hidden)).id();
684        world.entity_mut(id2).push_children(&[id3]);
685
686        let id4 = world.spawn(VisibilityBundle::default()).id();
687        world.entity_mut(id3).push_children(&[id4]);
688
689        // Test the hierarchy.
690
691        // Make sure the hierarchy is up-to-date.
692        schedule.run(&mut world);
693        world.clear_trackers();
694
695        let mut q = world.query::<Ref<InheritedVisibility>>();
696
697        assert!(!q.get(&world, id1).unwrap().is_changed());
698        assert!(!q.get(&world, id2).unwrap().is_changed());
699        assert!(!q.get(&world, id3).unwrap().is_changed());
700        assert!(!q.get(&world, id4).unwrap().is_changed());
701
702        world.clear_trackers();
703        world.entity_mut(id1).insert(Visibility::Hidden);
704        schedule.run(&mut world);
705
706        assert!(q.get(&world, id1).unwrap().is_changed());
707        assert!(q.get(&world, id2).unwrap().is_changed());
708        assert!(!q.get(&world, id3).unwrap().is_changed());
709        assert!(!q.get(&world, id4).unwrap().is_changed());
710
711        world.clear_trackers();
712        schedule.run(&mut world);
713
714        assert!(!q.get(&world, id1).unwrap().is_changed());
715        assert!(!q.get(&world, id2).unwrap().is_changed());
716        assert!(!q.get(&world, id3).unwrap().is_changed());
717        assert!(!q.get(&world, id4).unwrap().is_changed());
718
719        world.clear_trackers();
720        world.entity_mut(id3).insert(Visibility::Inherited);
721        schedule.run(&mut world);
722
723        assert!(!q.get(&world, id1).unwrap().is_changed());
724        assert!(!q.get(&world, id2).unwrap().is_changed());
725        assert!(!q.get(&world, id3).unwrap().is_changed());
726        assert!(!q.get(&world, id4).unwrap().is_changed());
727
728        world.clear_trackers();
729        world.entity_mut(id2).insert(Visibility::Visible);
730        schedule.run(&mut world);
731
732        assert!(!q.get(&world, id1).unwrap().is_changed());
733        assert!(q.get(&world, id2).unwrap().is_changed());
734        assert!(q.get(&world, id3).unwrap().is_changed());
735        assert!(q.get(&world, id4).unwrap().is_changed());
736
737        world.clear_trackers();
738        schedule.run(&mut world);
739
740        assert!(!q.get(&world, id1).unwrap().is_changed());
741        assert!(!q.get(&world, id2).unwrap().is_changed());
742        assert!(!q.get(&world, id3).unwrap().is_changed());
743        assert!(!q.get(&world, id4).unwrap().is_changed());
744    }
745
746    #[test]
747    fn visibility_propagation_with_invalid_parent() {
748        let mut world = World::new();
749        let mut schedule = Schedule::default();
750        schedule.add_systems(visibility_propagate_system);
751
752        let parent = world.spawn(()).id();
753        let child = world.spawn(VisibilityBundle::default()).id();
754        world.entity_mut(parent).push_children(&[child]);
755
756        schedule.run(&mut world);
757        world.clear_trackers();
758
759        let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
760        // defaults to same behavior of parent not found: visible = true
761        assert!(child_visible);
762    }
763
764    #[test]
765    fn ensure_visibility_enum_size() {
766        use std::mem;
767        assert_eq!(1, mem::size_of::<Visibility>());
768        assert_eq!(1, mem::size_of::<Option<Visibility>>());
769    }
770}