bevy_render/view/visibility/
mod.rs

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