1mod range;
2mod render_layers;
3
4use core::any::TypeId;
5
6use bevy_ecs::entity::{EntityHashMap, EntityHashSet};
7use bevy_ecs::lifecycle::HookContext;
8use bevy_ecs::world::DeferredWorld;
9use derive_more::derive::{Deref, DerefMut};
10pub use range::*;
11pub use render_layers::*;
12
13use bevy_app::{Plugin, PostUpdate};
14use bevy_asset::Assets;
15use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*};
16use bevy_reflect::{std_traits::ReflectDefault, Reflect};
17use bevy_transform::{components::GlobalTransform, TransformSystems};
18use bevy_utils::{Parallel, TypeIdMap};
19use smallvec::SmallVec;
20
21use crate::{
22 camera::Camera,
23 primitives::{Aabb, Frustum, MeshAabb, Sphere},
24 Projection,
25};
26use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, Mesh3d};
27
28#[derive(Component, Default)]
29pub struct NoCpuCulling;
30
31#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
39#[reflect(Component, Default, Debug, PartialEq, Clone)]
40#[require(InheritedVisibility, ViewVisibility)]
41pub enum Visibility {
42 #[default]
46 Inherited,
47 Hidden,
49 Visible,
54}
55
56impl Visibility {
57 #[inline]
60 pub fn toggle_inherited_visible(&mut self) {
61 *self = match *self {
62 Visibility::Inherited => Visibility::Visible,
63 Visibility::Visible => Visibility::Inherited,
64 _ => *self,
65 };
66 }
67 #[inline]
70 pub fn toggle_inherited_hidden(&mut self) {
71 *self = match *self {
72 Visibility::Inherited => Visibility::Hidden,
73 Visibility::Hidden => Visibility::Inherited,
74 _ => *self,
75 };
76 }
77 #[inline]
80 pub fn toggle_visible_hidden(&mut self) {
81 *self = match *self {
82 Visibility::Visible => Visibility::Hidden,
83 Visibility::Hidden => Visibility::Visible,
84 _ => *self,
85 };
86 }
87}
88
89impl PartialEq<Visibility> for &Visibility {
91 #[inline]
92 fn eq(&self, other: &Visibility) -> bool {
93 <Visibility as PartialEq<Visibility>>::eq(*self, other)
95 }
96}
97
98impl PartialEq<&Visibility> for Visibility {
100 #[inline]
101 fn eq(&self, other: &&Visibility) -> bool {
102 <Visibility as PartialEq<Visibility>>::eq(self, *other)
104 }
105}
106
107#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
114#[reflect(Component, Default, Debug, PartialEq, Clone)]
115#[component(on_insert = validate_parent_has_component::<Self>)]
116pub struct InheritedVisibility(bool);
117
118impl InheritedVisibility {
119 pub const HIDDEN: Self = Self(false);
121 pub const VISIBLE: Self = Self(true);
123
124 #[inline]
127 pub fn get(self) -> bool {
128 self.0
129 }
130}
131
132#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]
161#[reflect(Component, Default, Clone)]
162#[component(clone_behavior=Ignore)]
163pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);
164
165#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
176#[reflect(Component, Default, Debug, PartialEq, Clone)]
177pub struct ViewVisibility(bool);
178
179impl ViewVisibility {
180 pub const HIDDEN: Self = Self(false);
182
183 #[inline]
186 pub fn get(self) -> bool {
187 self.0
188 }
189
190 #[inline]
203 pub fn set(&mut self) {
204 self.0 = true;
205 }
206}
207
208#[derive(Debug, Component, Default, Reflect)]
216#[reflect(Component, Default, Debug)]
217pub struct NoFrustumCulling;
218
219#[derive(Clone, Component, Default, Debug, Reflect)]
229#[reflect(Component, Default, Debug, Clone)]
230pub struct VisibleEntities {
231 #[reflect(ignore, clone)]
232 pub entities: TypeIdMap<Vec<Entity>>,
233}
234
235impl VisibleEntities {
236 pub fn get(&self, type_id: TypeId) -> &[Entity] {
237 match self.entities.get(&type_id) {
238 Some(entities) => &entities[..],
239 None => &[],
240 }
241 }
242
243 pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
244 self.entities.entry(type_id).or_default()
245 }
246
247 pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
248 self.get(type_id).iter()
249 }
250
251 pub fn len(&self, type_id: TypeId) -> usize {
252 self.get(type_id).len()
253 }
254
255 pub fn is_empty(&self, type_id: TypeId) -> bool {
256 self.get(type_id).is_empty()
257 }
258
259 pub fn clear(&mut self, type_id: TypeId) {
260 self.get_mut(type_id).clear();
261 }
262
263 pub fn clear_all(&mut self) {
264 for entities in self.entities.values_mut() {
266 entities.clear();
267 }
268 }
269
270 pub fn push(&mut self, entity: Entity, type_id: TypeId) {
271 self.get_mut(type_id).push(entity);
272 }
273}
274
275#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]
280#[reflect(Component, Debug, Default, Clone)]
281pub struct VisibleMeshEntities {
282 #[reflect(ignore, clone)]
283 pub entities: Vec<Entity>,
284}
285
286#[derive(Component, Clone, Debug, Default, Reflect)]
287#[reflect(Component, Debug, Default, Clone)]
288pub struct CubemapVisibleEntities {
289 #[reflect(ignore, clone)]
290 data: [VisibleMeshEntities; 6],
291}
292
293impl CubemapVisibleEntities {
294 pub fn get(&self, i: usize) -> &VisibleMeshEntities {
295 &self.data[i]
296 }
297
298 pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities {
299 &mut self.data[i]
300 }
301
302 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleMeshEntities> {
303 self.data.iter()
304 }
305
306 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleMeshEntities> {
307 self.data.iter_mut()
308 }
309}
310
311#[derive(Component, Clone, Debug, Default, Reflect)]
312#[reflect(Component, Default, Clone)]
313pub struct CascadesVisibleEntities {
314 #[reflect(ignore, clone)]
316 pub entities: EntityHashMap<Vec<VisibleMeshEntities>>,
317}
318
319#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
320pub enum VisibilitySystems {
321 CalculateBounds,
324 UpdateFrusta,
326 VisibilityPropagate,
329 CheckVisibility,
336 MarkNewlyHiddenEntitiesInvisible,
340}
341
342pub struct VisibilityPlugin;
343
344impl Plugin for VisibilityPlugin {
345 fn build(&self, app: &mut bevy_app::App) {
346 use VisibilitySystems::*;
347
348 app.register_required_components::<Mesh3d, Visibility>()
349 .register_required_components::<Mesh3d, VisibilityClass>()
350 .register_required_components::<Mesh2d, Visibility>()
351 .register_required_components::<Mesh2d, VisibilityClass>()
352 .configure_sets(
353 PostUpdate,
354 (
355 CalculateBounds
356 .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),
357 UpdateFrusta,
358 VisibilityPropagate,
359 )
360 .before(CheckVisibility)
361 .after(TransformSystems::Propagate),
362 )
363 .configure_sets(
364 PostUpdate,
365 MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),
366 )
367 .init_resource::<PreviousVisibleEntities>()
368 .add_systems(
369 PostUpdate,
370 (
371 calculate_bounds.in_set(CalculateBounds),
372 (visibility_propagate_system, reset_view_visibility)
373 .in_set(VisibilityPropagate),
374 check_visibility.in_set(CheckVisibility),
375 mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),
376 ),
377 );
378 app.world_mut()
379 .register_component_hooks::<Mesh3d>()
380 .on_add(add_visibility_class::<Mesh3d>);
381 app.world_mut()
382 .register_component_hooks::<Mesh2d>()
383 .on_add(add_visibility_class::<Mesh2d>);
384 }
385}
386
387pub fn calculate_bounds(
392 mut commands: Commands,
393 meshes: Res<Assets<Mesh>>,
394 without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,
395) {
396 for (entity, mesh_handle) in &without_aabb {
397 if let Some(mesh) = meshes.get(mesh_handle)
398 && let Some(aabb) = mesh.compute_aabb()
399 {
400 commands.entity(entity).try_insert(aabb);
401 }
402 }
403}
404
405pub fn update_frusta(
409 mut views: Query<
410 (&GlobalTransform, &Projection, &mut Frustum),
411 Or<(Changed<GlobalTransform>, Changed<Projection>)>,
412 >,
413) {
414 for (transform, projection, mut frustum) in &mut views {
415 *frustum = projection.compute_frustum(transform);
416 }
417}
418
419fn visibility_propagate_system(
420 changed: Query<
421 (Entity, &Visibility, Option<&ChildOf>, Option<&Children>),
422 (
423 With<InheritedVisibility>,
424 Or<(Changed<Visibility>, Changed<ChildOf>)>,
425 ),
426 >,
427 mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
428 children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
429) {
430 for (entity, visibility, child_of, children) in &changed {
431 let is_visible = match visibility {
432 Visibility::Visible => true,
433 Visibility::Hidden => false,
434 Visibility::Inherited => child_of
436 .and_then(|c| visibility_query.get(c.parent()).ok())
437 .is_none_or(|(_, x)| x.get()),
438 };
439 let (_, mut inherited_visibility) = visibility_query
440 .get_mut(entity)
441 .expect("With<InheritedVisibility> ensures this query will return a value");
442
443 if inherited_visibility.get() != is_visible {
447 inherited_visibility.0 = is_visible;
448
449 for &child in children.into_iter().flatten() {
451 let _ =
452 propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
453 }
454 }
455 }
456}
457
458fn propagate_recursive(
459 parent_is_visible: bool,
460 entity: Entity,
461 visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
462 children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
463 ) -> Result<(), ()> {
466 let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
469
470 let is_visible = match visibility {
471 Visibility::Visible => true,
472 Visibility::Hidden => false,
473 Visibility::Inherited => parent_is_visible,
474 };
475
476 if inherited_visibility.get() != is_visible {
478 inherited_visibility.0 = is_visible;
479
480 for &child in children_query.get(entity).ok().into_iter().flatten() {
482 let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
483 }
484 }
485
486 Ok(())
487}
488
489#[derive(Resource, Default, Deref, DerefMut)]
495pub struct PreviousVisibleEntities(EntityHashSet);
496
497fn reset_view_visibility(
501 mut query: Query<(Entity, &ViewVisibility)>,
502 mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
503) {
504 previous_visible_entities.clear();
505
506 query.iter_mut().for_each(|(entity, view_visibility)| {
507 if view_visibility.get() {
509 previous_visible_entities.insert(entity);
510 }
511 });
512}
513
514pub fn check_visibility(
523 mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,
524 mut view_query: Query<(
525 Entity,
526 &mut VisibleEntities,
527 &Frustum,
528 Option<&RenderLayers>,
529 &Camera,
530 Has<NoCpuCulling>,
531 )>,
532 mut visible_aabb_query: Query<(
533 Entity,
534 &InheritedVisibility,
535 &mut ViewVisibility,
536 &VisibilityClass,
537 Option<&RenderLayers>,
538 Option<&Aabb>,
539 &GlobalTransform,
540 Has<NoFrustumCulling>,
541 Has<VisibilityRange>,
542 )>,
543 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
544 mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
545) {
546 let visible_entity_ranges = visible_entity_ranges.as_deref();
547
548 for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in
549 &mut view_query
550 {
551 if !camera.is_active {
552 continue;
553 }
554
555 let view_mask = maybe_view_mask.unwrap_or_default();
556
557 visible_aabb_query.par_iter_mut().for_each_init(
558 || thread_queues.borrow_local_mut(),
559 |queue, query_item| {
560 let (
561 entity,
562 inherited_visibility,
563 mut view_visibility,
564 visibility_class,
565 maybe_entity_mask,
566 maybe_model_aabb,
567 transform,
568 no_frustum_culling,
569 has_visibility_range,
570 ) = query_item;
571
572 if !inherited_visibility.get() {
575 return;
576 }
577
578 let entity_mask = maybe_entity_mask.unwrap_or_default();
579 if !view_mask.intersects(entity_mask) {
580 return;
581 }
582
583 if has_visibility_range
585 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
586 !visible_entity_ranges.entity_is_in_range_of_view(entity, view)
587 })
588 {
589 return;
590 }
591
592 if !no_frustum_culling
594 && !no_cpu_culling
595 && let Some(model_aabb) = maybe_model_aabb
596 {
597 let world_from_local = transform.affine();
598 let model_sphere = Sphere {
599 center: world_from_local.transform_point3a(model_aabb.center),
600 radius: transform.radius_vec3a(model_aabb.half_extents),
601 };
602 if !frustum.intersects_sphere(&model_sphere, false) {
604 return;
605 }
606 if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
608 return;
609 }
610 }
611
612 if !**view_visibility {
616 view_visibility.set();
617 }
618
619 for visibility_class_id in visibility_class.iter() {
622 queue.entry(*visibility_class_id).or_default().push(entity);
623 }
624 },
625 );
626
627 visible_entities.clear_all();
628
629 for class_queues in thread_queues.iter_mut() {
631 for (class, entities) in class_queues {
632 let visible_entities_for_class = visible_entities.get_mut(*class);
633 for entity in entities.drain(..) {
634 previous_visible_entities.remove(&entity);
640
641 visible_entities_for_class.push(entity);
642 }
643 }
644 }
645 }
646}
647
648fn mark_newly_hidden_entities_invisible(
656 mut view_visibilities: Query<&mut ViewVisibility>,
657 mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
658) {
659 for entity in previous_visible_entities.drain() {
662 if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) {
663 *view_visibility = ViewVisibility::HIDDEN;
664 }
665 }
666}
667
668pub fn add_visibility_class<C>(
682 mut world: DeferredWorld<'_>,
683 HookContext { entity, .. }: HookContext,
684) where
685 C: 'static,
686{
687 if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
688 visibility_class.push(TypeId::of::<C>());
689 }
690}
691
692#[cfg(test)]
693mod test {
694 use super::*;
695 use bevy_app::prelude::*;
696
697 #[test]
698 fn visibility_propagation() {
699 let mut app = App::new();
700 app.add_systems(Update, visibility_propagate_system);
701
702 let root1 = app.world_mut().spawn(Visibility::Hidden).id();
703 let root1_child1 = app.world_mut().spawn(Visibility::default()).id();
704 let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();
705 let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
706 let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
707
708 app.world_mut()
709 .entity_mut(root1)
710 .add_children(&[root1_child1, root1_child2]);
711 app.world_mut()
712 .entity_mut(root1_child1)
713 .add_children(&[root1_child1_grandchild1]);
714 app.world_mut()
715 .entity_mut(root1_child2)
716 .add_children(&[root1_child2_grandchild1]);
717
718 let root2 = app.world_mut().spawn(Visibility::default()).id();
719 let root2_child1 = app.world_mut().spawn(Visibility::default()).id();
720 let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();
721 let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
722 let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
723
724 app.world_mut()
725 .entity_mut(root2)
726 .add_children(&[root2_child1, root2_child2]);
727 app.world_mut()
728 .entity_mut(root2_child1)
729 .add_children(&[root2_child1_grandchild1]);
730 app.world_mut()
731 .entity_mut(root2_child2)
732 .add_children(&[root2_child2_grandchild1]);
733
734 app.update();
735
736 let is_visible = |e: Entity| {
737 app.world()
738 .entity(e)
739 .get::<InheritedVisibility>()
740 .unwrap()
741 .get()
742 };
743 assert!(
744 !is_visible(root1),
745 "invisibility propagates down tree from root"
746 );
747 assert!(
748 !is_visible(root1_child1),
749 "invisibility propagates down tree from root"
750 );
751 assert!(
752 !is_visible(root1_child2),
753 "invisibility propagates down tree from root"
754 );
755 assert!(
756 !is_visible(root1_child1_grandchild1),
757 "invisibility propagates down tree from root"
758 );
759 assert!(
760 !is_visible(root1_child2_grandchild1),
761 "invisibility propagates down tree from root"
762 );
763
764 assert!(
765 is_visible(root2),
766 "visibility propagates down tree from root"
767 );
768 assert!(
769 is_visible(root2_child1),
770 "visibility propagates down tree from root"
771 );
772 assert!(
773 !is_visible(root2_child2),
774 "visibility propagates down tree from root, but local invisibility is preserved"
775 );
776 assert!(
777 is_visible(root2_child1_grandchild1),
778 "visibility propagates down tree from root"
779 );
780 assert!(
781 !is_visible(root2_child2_grandchild1),
782 "child's invisibility propagates down to grandchild"
783 );
784 }
785
786 #[test]
787 fn test_visibility_propagation_on_parent_change() {
788 let mut app = App::new();
790
791 app.add_systems(Update, visibility_propagate_system);
792
793 let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();
795 let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();
796 let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();
797 let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();
798
799 app.world_mut()
801 .entity_mut(parent1)
802 .add_children(&[child1, child2]);
803
804 app.update();
806
807 app.world_mut()
809 .entity_mut(parent2)
810 .insert(Visibility::Visible);
811 app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); app.update();
816
817 let is_visible = |e: Entity| {
818 app.world()
819 .entity(e)
820 .get::<InheritedVisibility>()
821 .unwrap()
822 .get()
823 };
824
825 assert!(
828 !is_visible(child1),
829 "Child1 should inherit visibility from parent"
830 );
831
832 assert!(
833 is_visible(child2),
834 "Child2 should inherit visibility from parent"
835 );
836 }
837
838 #[test]
839 fn visibility_propagation_unconditional_visible() {
840 use Visibility::{Hidden, Inherited, Visible};
841
842 let mut app = App::new();
843 app.add_systems(Update, visibility_propagate_system);
844
845 let root1 = app.world_mut().spawn(Visible).id();
846 let root1_child1 = app.world_mut().spawn(Inherited).id();
847 let root1_child2 = app.world_mut().spawn(Hidden).id();
848 let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();
849 let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();
850
851 let root2 = app.world_mut().spawn(Inherited).id();
852 let root3 = app.world_mut().spawn(Hidden).id();
853
854 app.world_mut()
855 .entity_mut(root1)
856 .add_children(&[root1_child1, root1_child2]);
857 app.world_mut()
858 .entity_mut(root1_child1)
859 .add_children(&[root1_child1_grandchild1]);
860 app.world_mut()
861 .entity_mut(root1_child2)
862 .add_children(&[root1_child2_grandchild1]);
863
864 app.update();
865
866 let is_visible = |e: Entity| {
867 app.world()
868 .entity(e)
869 .get::<InheritedVisibility>()
870 .unwrap()
871 .get()
872 };
873 assert!(
874 is_visible(root1),
875 "an unconditionally visible root is visible"
876 );
877 assert!(
878 is_visible(root1_child1),
879 "an inheriting child of an unconditionally visible parent is visible"
880 );
881 assert!(
882 !is_visible(root1_child2),
883 "a hidden child on an unconditionally visible parent is hidden"
884 );
885 assert!(
886 is_visible(root1_child1_grandchild1),
887 "an unconditionally visible child of an inheriting parent is visible"
888 );
889 assert!(
890 is_visible(root1_child2_grandchild1),
891 "an unconditionally visible child of a hidden parent is visible"
892 );
893 assert!(is_visible(root2), "an inheriting root is visible");
894 assert!(!is_visible(root3), "a hidden root is hidden");
895 }
896
897 #[test]
898 fn visibility_propagation_change_detection() {
899 let mut world = World::new();
900 let mut schedule = Schedule::default();
901 schedule.add_systems(visibility_propagate_system);
902
903 let id1 = world.spawn(Visibility::default()).id();
906
907 let id2 = world.spawn(Visibility::default()).id();
908 world.entity_mut(id1).add_children(&[id2]);
909
910 let id3 = world.spawn(Visibility::Hidden).id();
911 world.entity_mut(id2).add_children(&[id3]);
912
913 let id4 = world.spawn(Visibility::default()).id();
914 world.entity_mut(id3).add_children(&[id4]);
915
916 schedule.run(&mut world);
920 world.clear_trackers();
921
922 let mut q = world.query::<Ref<InheritedVisibility>>();
923
924 assert!(!q.get(&world, id1).unwrap().is_changed());
925 assert!(!q.get(&world, id2).unwrap().is_changed());
926 assert!(!q.get(&world, id3).unwrap().is_changed());
927 assert!(!q.get(&world, id4).unwrap().is_changed());
928
929 world.clear_trackers();
930 world.entity_mut(id1).insert(Visibility::Hidden);
931 schedule.run(&mut world);
932
933 assert!(q.get(&world, id1).unwrap().is_changed());
934 assert!(q.get(&world, id2).unwrap().is_changed());
935 assert!(!q.get(&world, id3).unwrap().is_changed());
936 assert!(!q.get(&world, id4).unwrap().is_changed());
937
938 world.clear_trackers();
939 schedule.run(&mut world);
940
941 assert!(!q.get(&world, id1).unwrap().is_changed());
942 assert!(!q.get(&world, id2).unwrap().is_changed());
943 assert!(!q.get(&world, id3).unwrap().is_changed());
944 assert!(!q.get(&world, id4).unwrap().is_changed());
945
946 world.clear_trackers();
947 world.entity_mut(id3).insert(Visibility::Inherited);
948 schedule.run(&mut world);
949
950 assert!(!q.get(&world, id1).unwrap().is_changed());
951 assert!(!q.get(&world, id2).unwrap().is_changed());
952 assert!(!q.get(&world, id3).unwrap().is_changed());
953 assert!(!q.get(&world, id4).unwrap().is_changed());
954
955 world.clear_trackers();
956 world.entity_mut(id2).insert(Visibility::Visible);
957 schedule.run(&mut world);
958
959 assert!(!q.get(&world, id1).unwrap().is_changed());
960 assert!(q.get(&world, id2).unwrap().is_changed());
961 assert!(q.get(&world, id3).unwrap().is_changed());
962 assert!(q.get(&world, id4).unwrap().is_changed());
963
964 world.clear_trackers();
965 schedule.run(&mut world);
966
967 assert!(!q.get(&world, id1).unwrap().is_changed());
968 assert!(!q.get(&world, id2).unwrap().is_changed());
969 assert!(!q.get(&world, id3).unwrap().is_changed());
970 assert!(!q.get(&world, id4).unwrap().is_changed());
971 }
972
973 #[test]
974 fn visibility_propagation_with_invalid_parent() {
975 let mut world = World::new();
976 let mut schedule = Schedule::default();
977 schedule.add_systems(visibility_propagate_system);
978
979 let parent = world.spawn(()).id();
980 let child = world.spawn(Visibility::default()).id();
981 world.entity_mut(parent).add_children(&[child]);
982
983 schedule.run(&mut world);
984 world.clear_trackers();
985
986 let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
987 assert!(child_visible);
989 }
990
991 #[test]
992 fn ensure_visibility_enum_size() {
993 assert_eq!(1, size_of::<Visibility>());
994 assert_eq!(1, size_of::<Option<Visibility>>());
995 }
996
997 #[derive(Component, Default, Clone, Reflect)]
998 #[require(VisibilityClass)]
999 #[reflect(Component, Default, Clone)]
1000 #[component(on_add = add_visibility_class::<Self>)]
1001 struct TestVisibilityClassHook;
1002
1003 #[test]
1004 fn test_add_visibility_class_hook() {
1005 let mut world = World::new();
1006 let entity = world.spawn(TestVisibilityClassHook).id();
1007 let entity_clone = world.spawn_empty().id();
1008 world
1009 .entity_mut(entity)
1010 .clone_with_opt_out(entity_clone, |_| {});
1011
1012 let entity_visibility_class = world.entity(entity).get::<VisibilityClass>().unwrap();
1013 assert_eq!(entity_visibility_class.len(), 1);
1014
1015 let entity_clone_visibility_class =
1016 world.entity(entity_clone).get::<VisibilityClass>().unwrap();
1017 assert_eq!(entity_clone_visibility_class.len(), 1);
1018 }
1019}