1mod range;
2mod render_layers;
3
4use core::any::TypeId;
5
6use bevy_ecs::entity::EntityHashMap;
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::prelude::AssetChanged;
15use bevy_asset::{AssetEventSystems, Assets};
16use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*};
17use bevy_reflect::{std_traits::ReflectDefault, Reflect};
18use bevy_transform::{components::GlobalTransform, TransformSystems};
19use bevy_utils::{Parallel, TypeIdMap};
20use smallvec::SmallVec;
21
22use crate::{
23 camera::Camera,
24 primitives::{Aabb, Frustum, MeshAabb, Sphere},
25 Projection,
26};
27use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, Mesh3d};
28
29#[derive(Component, Default)]
30pub struct NoCpuCulling;
31
32#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
40#[reflect(Component, Default, Debug, PartialEq, Clone)]
41#[require(InheritedVisibility, ViewVisibility)]
42pub enum Visibility {
43 #[default]
47 Inherited,
48 Hidden,
50 Visible,
55}
56
57impl Visibility {
58 #[inline]
61 pub fn toggle_inherited_visible(&mut self) {
62 *self = match *self {
63 Visibility::Inherited => Visibility::Visible,
64 Visibility::Visible => Visibility::Inherited,
65 _ => *self,
66 };
67 }
68 #[inline]
71 pub fn toggle_inherited_hidden(&mut self) {
72 *self = match *self {
73 Visibility::Inherited => Visibility::Hidden,
74 Visibility::Hidden => Visibility::Inherited,
75 _ => *self,
76 };
77 }
78 #[inline]
81 pub fn toggle_visible_hidden(&mut self) {
82 *self = match *self {
83 Visibility::Visible => Visibility::Hidden,
84 Visibility::Hidden => Visibility::Visible,
85 _ => *self,
86 };
87 }
88}
89
90impl PartialEq<Visibility> for &Visibility {
92 #[inline]
93 fn eq(&self, other: &Visibility) -> bool {
94 <Visibility as PartialEq<Visibility>>::eq(*self, other)
96 }
97}
98
99impl PartialEq<&Visibility> for Visibility {
101 #[inline]
102 fn eq(&self, other: &&Visibility) -> bool {
103 <Visibility as PartialEq<Visibility>>::eq(self, *other)
105 }
106}
107
108#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
115#[reflect(Component, Default, Debug, PartialEq, Clone)]
116#[component(on_insert = validate_parent_has_component::<Self>)]
117pub struct InheritedVisibility(bool);
118
119impl InheritedVisibility {
120 pub const HIDDEN: Self = Self(false);
122 pub const VISIBLE: Self = Self(true);
124
125 #[inline]
128 pub fn get(self) -> bool {
129 self.0
130 }
131}
132
133#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]
162#[reflect(Component, Default, Clone)]
163#[component(clone_behavior=Ignore)]
164pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);
165
166#[derive(Component, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
180#[reflect(Component, Default, Debug, PartialEq, Clone)]
181pub struct ViewVisibility(
182 u8,
197);
198
199impl ViewVisibility {
200 pub const HIDDEN: Self = Self(0);
202
203 #[inline]
206 pub fn get(self) -> bool {
207 self.0 & 1 != 0
208 }
209
210 #[inline]
212 fn was_visible_now_hidden(self) -> bool {
213 (self.0 & 0b11) == 0b10
215 }
216
217 #[inline]
218 fn update(&mut self) {
219 self.0 = (self.0 & 1) << 1;
222 }
223}
224
225pub trait SetViewVisibility {
226 fn set_visible(&mut self);
236}
237
238impl<'a> SetViewVisibility for Mut<'a, ViewVisibility> {
239 #[inline]
240 fn set_visible(&mut self) {
241 if self.0 & 1 == 0 {
244 if self.0 & 2 != 0 {
245 self.bypass_change_detection().0 |= 1;
248 } else {
249 self.0 |= 1;
252 }
253 }
254 }
255}
256
257#[derive(Debug, Component, Default, Reflect)]
265#[reflect(Component, Default, Debug)]
266pub struct NoFrustumCulling;
267
268#[derive(Clone, Component, Default, Debug, Reflect)]
278#[reflect(Component, Default, Debug, Clone)]
279pub struct VisibleEntities {
280 #[reflect(ignore, clone)]
281 pub entities: TypeIdMap<Vec<Entity>>,
282}
283
284impl VisibleEntities {
285 pub fn get(&self, type_id: TypeId) -> &[Entity] {
286 match self.entities.get(&type_id) {
287 Some(entities) => &entities[..],
288 None => &[],
289 }
290 }
291
292 pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
293 self.entities.entry(type_id).or_default()
294 }
295
296 pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
297 self.get(type_id).iter()
298 }
299
300 pub fn len(&self, type_id: TypeId) -> usize {
301 self.get(type_id).len()
302 }
303
304 pub fn is_empty(&self, type_id: TypeId) -> bool {
305 self.get(type_id).is_empty()
306 }
307
308 pub fn clear(&mut self, type_id: TypeId) {
309 self.get_mut(type_id).clear();
310 }
311
312 pub fn clear_all(&mut self) {
313 for entities in self.entities.values_mut() {
315 entities.clear();
316 }
317 }
318
319 pub fn push(&mut self, entity: Entity, type_id: TypeId) {
320 self.get_mut(type_id).push(entity);
321 }
322}
323
324#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]
329#[reflect(Component, Debug, Default, Clone)]
330pub struct VisibleMeshEntities {
331 #[reflect(ignore, clone)]
332 pub entities: Vec<Entity>,
333}
334
335#[derive(Component, Clone, Debug, Default, Reflect)]
336#[reflect(Component, Debug, Default, Clone)]
337pub struct CubemapVisibleEntities {
338 #[reflect(ignore, clone)]
339 data: [VisibleMeshEntities; 6],
340}
341
342impl CubemapVisibleEntities {
343 pub fn get(&self, i: usize) -> &VisibleMeshEntities {
344 &self.data[i]
345 }
346
347 pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities {
348 &mut self.data[i]
349 }
350
351 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleMeshEntities> {
352 self.data.iter()
353 }
354
355 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleMeshEntities> {
356 self.data.iter_mut()
357 }
358}
359
360#[derive(Component, Clone, Debug, Default, Reflect)]
361#[reflect(Component, Default, Clone)]
362pub struct CascadesVisibleEntities {
363 #[reflect(ignore, clone)]
365 pub entities: EntityHashMap<Vec<VisibleMeshEntities>>,
366}
367
368#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
369pub enum VisibilitySystems {
370 CalculateBounds,
373 UpdateFrusta,
375 VisibilityPropagate,
378 CheckVisibility,
385 MarkNewlyHiddenEntitiesInvisible,
389}
390
391pub struct VisibilityPlugin;
392
393impl Plugin for VisibilityPlugin {
394 fn build(&self, app: &mut bevy_app::App) {
395 use VisibilitySystems::*;
396
397 app.register_required_components::<Mesh3d, Visibility>()
398 .register_required_components::<Mesh3d, VisibilityClass>()
399 .register_required_components::<Mesh2d, Visibility>()
400 .register_required_components::<Mesh2d, VisibilityClass>()
401 .configure_sets(
402 PostUpdate,
403 (UpdateFrusta, VisibilityPropagate)
404 .before(CheckVisibility)
405 .after(TransformSystems::Propagate),
406 )
407 .configure_sets(
408 PostUpdate,
409 MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),
410 )
411 .configure_sets(
412 PostUpdate,
413 (CalculateBounds)
414 .before(CheckVisibility)
415 .after(TransformSystems::Propagate)
416 .after(AssetEventSystems)
417 .ambiguous_with(CalculateBounds)
418 .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),
419 )
420 .add_systems(
421 PostUpdate,
422 (
423 calculate_bounds.in_set(CalculateBounds),
424 (visibility_propagate_system, reset_view_visibility)
425 .in_set(VisibilityPropagate),
426 check_visibility.in_set(CheckVisibility),
427 mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),
428 ),
429 );
430 app.world_mut()
431 .register_component_hooks::<Mesh3d>()
432 .on_add(add_visibility_class::<Mesh3d>);
433 app.world_mut()
434 .register_component_hooks::<Mesh2d>()
435 .on_add(add_visibility_class::<Mesh2d>);
436 }
437}
438
439#[derive(Component, Clone, Debug, Default, Reflect)]
445pub struct NoAutoAabb;
446
447pub fn calculate_bounds(
452 mut commands: Commands,
453 meshes: Res<Assets<Mesh>>,
454 new_aabb: Query<
455 (Entity, &Mesh3d),
456 (
457 Without<Aabb>,
458 Without<NoFrustumCulling>,
459 Without<NoAutoAabb>,
460 ),
461 >,
462 mut update_aabb: Query<
463 (&Mesh3d, &mut Aabb),
464 (
465 Or<(AssetChanged<Mesh3d>, Changed<Mesh3d>)>,
466 Without<NoFrustumCulling>,
467 Without<NoAutoAabb>,
468 ),
469 >,
470) {
471 for (entity, mesh_handle) in &new_aabb {
472 if let Some(mesh) = meshes.get(mesh_handle)
473 && let Some(aabb) = mesh.compute_aabb()
474 {
475 commands.entity(entity).try_insert(aabb);
476 }
477 }
478
479 update_aabb
480 .par_iter_mut()
481 .for_each(|(mesh_handle, mut old_aabb)| {
482 if let Some(aabb) = meshes.get(mesh_handle).and_then(MeshAabb::compute_aabb) {
483 *old_aabb = aabb;
484 }
485 });
486}
487
488pub fn update_frusta(
492 mut views: Query<
493 (&GlobalTransform, &Projection, &mut Frustum),
494 Or<(Changed<GlobalTransform>, Changed<Projection>)>,
495 >,
496) {
497 for (transform, projection, mut frustum) in &mut views {
498 *frustum = projection.compute_frustum(transform);
499 }
500}
501
502fn visibility_propagate_system(
503 changed: Query<
504 (Entity, &Visibility, Option<&ChildOf>, Option<&Children>),
505 (
506 With<InheritedVisibility>,
507 Or<(Changed<Visibility>, Changed<ChildOf>)>,
508 ),
509 >,
510 mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
511 children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
512) {
513 for (entity, visibility, child_of, children) in &changed {
514 let is_visible = match visibility {
515 Visibility::Visible => true,
516 Visibility::Hidden => false,
517 Visibility::Inherited => child_of
519 .and_then(|c| visibility_query.get(c.parent()).ok())
520 .is_none_or(|(_, x)| x.get()),
521 };
522 let (_, mut inherited_visibility) = visibility_query
523 .get_mut(entity)
524 .expect("With<InheritedVisibility> ensures this query will return a value");
525
526 if inherited_visibility.get() != is_visible {
530 inherited_visibility.0 = is_visible;
531
532 for &child in children.into_iter().flatten() {
534 let _ =
535 propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
536 }
537 }
538 }
539}
540
541fn propagate_recursive(
542 parent_is_visible: bool,
543 entity: Entity,
544 visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
545 children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
546 ) -> Result<(), ()> {
549 let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
552
553 let is_visible = match visibility {
554 Visibility::Visible => true,
555 Visibility::Hidden => false,
556 Visibility::Inherited => parent_is_visible,
557 };
558
559 if inherited_visibility.get() != is_visible {
561 inherited_visibility.0 = is_visible;
562
563 for &child in children_query.get(entity).ok().into_iter().flatten() {
565 let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
566 }
567 }
568
569 Ok(())
570}
571
572fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {
575 query.par_iter_mut().for_each(|mut view_visibility| {
576 view_visibility.bypass_change_detection().update();
577 });
578}
579
580pub fn check_visibility(
589 mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,
590 mut view_query: Query<(
591 Entity,
592 &mut VisibleEntities,
593 &Frustum,
594 Option<&RenderLayers>,
595 &Camera,
596 Has<NoCpuCulling>,
597 )>,
598 mut visible_aabb_query: Query<(
599 Entity,
600 &InheritedVisibility,
601 &mut ViewVisibility,
602 Option<&VisibilityClass>,
603 Option<&RenderLayers>,
604 Option<&Aabb>,
605 &GlobalTransform,
606 Has<NoFrustumCulling>,
607 Has<VisibilityRange>,
608 )>,
609 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
610) {
611 let visible_entity_ranges = visible_entity_ranges.as_deref();
612
613 for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in
614 &mut view_query
615 {
616 if !camera.is_active {
617 continue;
618 }
619
620 let view_mask = maybe_view_mask.unwrap_or_default();
621
622 visible_aabb_query.par_iter_mut().for_each_init(
623 || thread_queues.borrow_local_mut(),
624 |queue, query_item| {
625 let (
626 entity,
627 inherited_visibility,
628 mut view_visibility,
629 visibility_class,
630 maybe_entity_mask,
631 maybe_model_aabb,
632 transform,
633 no_frustum_culling,
634 has_visibility_range,
635 ) = query_item;
636
637 if !inherited_visibility.get() {
640 return;
641 }
642
643 let entity_mask = maybe_entity_mask.unwrap_or_default();
644 if !view_mask.intersects(entity_mask) {
645 return;
646 }
647
648 if has_visibility_range
650 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
651 !visible_entity_ranges.entity_is_in_range_of_view(entity, view)
652 })
653 {
654 return;
655 }
656
657 if !no_frustum_culling
659 && !no_cpu_culling
660 && let Some(model_aabb) = maybe_model_aabb
661 {
662 let world_from_local = transform.affine();
663 let model_sphere = Sphere {
664 center: world_from_local.transform_point3a(model_aabb.center),
665 radius: transform.radius_vec3a(model_aabb.half_extents),
666 };
667 if !frustum.intersects_sphere(&model_sphere, false) {
669 return;
670 }
671 if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
673 return;
674 }
675 }
676
677 view_visibility.set_visible();
678
679 if let Some(visibility_class) = visibility_class {
684 for visibility_class_id in visibility_class.iter() {
686 queue.entry(*visibility_class_id).or_default().push(entity);
687 }
688 }
689 },
690 );
691
692 visible_entities.clear_all();
693
694 for class_queues in thread_queues.iter_mut() {
696 for (class, entities) in class_queues {
697 visible_entities.get_mut(*class).append(entities);
698 }
699 }
700 }
701}
702
703fn mark_newly_hidden_entities_invisible(mut view_visibilities: Query<&mut ViewVisibility>) {
707 view_visibilities
708 .par_iter_mut()
709 .for_each(|mut view_visibility| {
710 if view_visibility.as_ref().was_visible_now_hidden() {
711 *view_visibility = ViewVisibility::HIDDEN;
712 }
713 });
714}
715
716pub fn add_visibility_class<C>(
730 mut world: DeferredWorld<'_>,
731 HookContext { entity, .. }: HookContext,
732) where
733 C: 'static,
734{
735 if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
736 visibility_class.push(TypeId::of::<C>());
737 }
738}
739
740#[cfg(test)]
741mod test {
742 use super::*;
743 use bevy_app::prelude::*;
744
745 #[test]
746 fn visibility_propagation() {
747 let mut app = App::new();
748 app.add_systems(Update, visibility_propagate_system);
749
750 let root1 = app.world_mut().spawn(Visibility::Hidden).id();
751 let root1_child1 = app.world_mut().spawn(Visibility::default()).id();
752 let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();
753 let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
754 let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
755
756 app.world_mut()
757 .entity_mut(root1)
758 .add_children(&[root1_child1, root1_child2]);
759 app.world_mut()
760 .entity_mut(root1_child1)
761 .add_children(&[root1_child1_grandchild1]);
762 app.world_mut()
763 .entity_mut(root1_child2)
764 .add_children(&[root1_child2_grandchild1]);
765
766 let root2 = app.world_mut().spawn(Visibility::default()).id();
767 let root2_child1 = app.world_mut().spawn(Visibility::default()).id();
768 let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();
769 let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
770 let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
771
772 app.world_mut()
773 .entity_mut(root2)
774 .add_children(&[root2_child1, root2_child2]);
775 app.world_mut()
776 .entity_mut(root2_child1)
777 .add_children(&[root2_child1_grandchild1]);
778 app.world_mut()
779 .entity_mut(root2_child2)
780 .add_children(&[root2_child2_grandchild1]);
781
782 app.update();
783
784 let is_visible = |e: Entity| {
785 app.world()
786 .entity(e)
787 .get::<InheritedVisibility>()
788 .unwrap()
789 .get()
790 };
791 assert!(
792 !is_visible(root1),
793 "invisibility propagates down tree from root"
794 );
795 assert!(
796 !is_visible(root1_child1),
797 "invisibility propagates down tree from root"
798 );
799 assert!(
800 !is_visible(root1_child2),
801 "invisibility propagates down tree from root"
802 );
803 assert!(
804 !is_visible(root1_child1_grandchild1),
805 "invisibility propagates down tree from root"
806 );
807 assert!(
808 !is_visible(root1_child2_grandchild1),
809 "invisibility propagates down tree from root"
810 );
811
812 assert!(
813 is_visible(root2),
814 "visibility propagates down tree from root"
815 );
816 assert!(
817 is_visible(root2_child1),
818 "visibility propagates down tree from root"
819 );
820 assert!(
821 !is_visible(root2_child2),
822 "visibility propagates down tree from root, but local invisibility is preserved"
823 );
824 assert!(
825 is_visible(root2_child1_grandchild1),
826 "visibility propagates down tree from root"
827 );
828 assert!(
829 !is_visible(root2_child2_grandchild1),
830 "child's invisibility propagates down to grandchild"
831 );
832 }
833
834 #[test]
835 fn test_visibility_propagation_on_parent_change() {
836 let mut app = App::new();
838
839 app.add_systems(Update, visibility_propagate_system);
840
841 let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();
843 let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();
844 let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();
845 let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();
846
847 app.world_mut()
849 .entity_mut(parent1)
850 .add_children(&[child1, child2]);
851
852 app.update();
854
855 app.world_mut()
857 .entity_mut(parent2)
858 .insert(Visibility::Visible);
859 app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); app.update();
864
865 let is_visible = |e: Entity| {
866 app.world()
867 .entity(e)
868 .get::<InheritedVisibility>()
869 .unwrap()
870 .get()
871 };
872
873 assert!(
876 !is_visible(child1),
877 "Child1 should inherit visibility from parent"
878 );
879
880 assert!(
881 is_visible(child2),
882 "Child2 should inherit visibility from parent"
883 );
884 }
885
886 #[test]
887 fn visibility_propagation_unconditional_visible() {
888 use Visibility::{Hidden, Inherited, Visible};
889
890 let mut app = App::new();
891 app.add_systems(Update, visibility_propagate_system);
892
893 let root1 = app.world_mut().spawn(Visible).id();
894 let root1_child1 = app.world_mut().spawn(Inherited).id();
895 let root1_child2 = app.world_mut().spawn(Hidden).id();
896 let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();
897 let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();
898
899 let root2 = app.world_mut().spawn(Inherited).id();
900 let root3 = app.world_mut().spawn(Hidden).id();
901
902 app.world_mut()
903 .entity_mut(root1)
904 .add_children(&[root1_child1, root1_child2]);
905 app.world_mut()
906 .entity_mut(root1_child1)
907 .add_children(&[root1_child1_grandchild1]);
908 app.world_mut()
909 .entity_mut(root1_child2)
910 .add_children(&[root1_child2_grandchild1]);
911
912 app.update();
913
914 let is_visible = |e: Entity| {
915 app.world()
916 .entity(e)
917 .get::<InheritedVisibility>()
918 .unwrap()
919 .get()
920 };
921 assert!(
922 is_visible(root1),
923 "an unconditionally visible root is visible"
924 );
925 assert!(
926 is_visible(root1_child1),
927 "an inheriting child of an unconditionally visible parent is visible"
928 );
929 assert!(
930 !is_visible(root1_child2),
931 "a hidden child on an unconditionally visible parent is hidden"
932 );
933 assert!(
934 is_visible(root1_child1_grandchild1),
935 "an unconditionally visible child of an inheriting parent is visible"
936 );
937 assert!(
938 is_visible(root1_child2_grandchild1),
939 "an unconditionally visible child of a hidden parent is visible"
940 );
941 assert!(is_visible(root2), "an inheriting root is visible");
942 assert!(!is_visible(root3), "a hidden root is hidden");
943 }
944
945 #[test]
946 fn visibility_propagation_change_detection() {
947 let mut world = World::new();
948 let mut schedule = Schedule::default();
949 schedule.add_systems(visibility_propagate_system);
950
951 let id1 = world.spawn(Visibility::default()).id();
954
955 let id2 = world.spawn(Visibility::default()).id();
956 world.entity_mut(id1).add_children(&[id2]);
957
958 let id3 = world.spawn(Visibility::Hidden).id();
959 world.entity_mut(id2).add_children(&[id3]);
960
961 let id4 = world.spawn(Visibility::default()).id();
962 world.entity_mut(id3).add_children(&[id4]);
963
964 schedule.run(&mut world);
968 world.clear_trackers();
969
970 let mut q = world.query::<Ref<InheritedVisibility>>();
971
972 assert!(!q.get(&world, id1).unwrap().is_changed());
973 assert!(!q.get(&world, id2).unwrap().is_changed());
974 assert!(!q.get(&world, id3).unwrap().is_changed());
975 assert!(!q.get(&world, id4).unwrap().is_changed());
976
977 world.clear_trackers();
978 world.entity_mut(id1).insert(Visibility::Hidden);
979 schedule.run(&mut world);
980
981 assert!(q.get(&world, id1).unwrap().is_changed());
982 assert!(q.get(&world, id2).unwrap().is_changed());
983 assert!(!q.get(&world, id3).unwrap().is_changed());
984 assert!(!q.get(&world, id4).unwrap().is_changed());
985
986 world.clear_trackers();
987 schedule.run(&mut world);
988
989 assert!(!q.get(&world, id1).unwrap().is_changed());
990 assert!(!q.get(&world, id2).unwrap().is_changed());
991 assert!(!q.get(&world, id3).unwrap().is_changed());
992 assert!(!q.get(&world, id4).unwrap().is_changed());
993
994 world.clear_trackers();
995 world.entity_mut(id3).insert(Visibility::Inherited);
996 schedule.run(&mut world);
997
998 assert!(!q.get(&world, id1).unwrap().is_changed());
999 assert!(!q.get(&world, id2).unwrap().is_changed());
1000 assert!(!q.get(&world, id3).unwrap().is_changed());
1001 assert!(!q.get(&world, id4).unwrap().is_changed());
1002
1003 world.clear_trackers();
1004 world.entity_mut(id2).insert(Visibility::Visible);
1005 schedule.run(&mut world);
1006
1007 assert!(!q.get(&world, id1).unwrap().is_changed());
1008 assert!(q.get(&world, id2).unwrap().is_changed());
1009 assert!(q.get(&world, id3).unwrap().is_changed());
1010 assert!(q.get(&world, id4).unwrap().is_changed());
1011
1012 world.clear_trackers();
1013 schedule.run(&mut world);
1014
1015 assert!(!q.get(&world, id1).unwrap().is_changed());
1016 assert!(!q.get(&world, id2).unwrap().is_changed());
1017 assert!(!q.get(&world, id3).unwrap().is_changed());
1018 assert!(!q.get(&world, id4).unwrap().is_changed());
1019 }
1020
1021 #[test]
1022 fn visibility_propagation_with_invalid_parent() {
1023 let mut world = World::new();
1024 let mut schedule = Schedule::default();
1025 schedule.add_systems(visibility_propagate_system);
1026
1027 let parent = world.spawn(()).id();
1028 let child = world.spawn(Visibility::default()).id();
1029 world.entity_mut(parent).add_children(&[child]);
1030
1031 schedule.run(&mut world);
1032 world.clear_trackers();
1033
1034 let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
1035 assert!(child_visible);
1037 }
1038
1039 #[test]
1040 fn ensure_visibility_enum_size() {
1041 assert_eq!(1, size_of::<Visibility>());
1042 assert_eq!(1, size_of::<Option<Visibility>>());
1043 }
1044
1045 #[derive(Component, Default, Clone, Reflect)]
1046 #[require(VisibilityClass)]
1047 #[reflect(Component, Default, Clone)]
1048 #[component(on_add = add_visibility_class::<Self>)]
1049 struct TestVisibilityClassHook;
1050
1051 #[test]
1052 fn test_add_visibility_class_hook() {
1053 let mut world = World::new();
1054 let entity = world.spawn(TestVisibilityClassHook).id();
1055 let entity_clone = world.spawn_empty().id();
1056 world
1057 .entity_mut(entity)
1058 .clone_with_opt_out(entity_clone, |_| {});
1059
1060 let entity_visibility_class = world.entity(entity).get::<VisibilityClass>().unwrap();
1061 assert_eq!(entity_visibility_class.len(), 1);
1062
1063 let entity_clone_visibility_class =
1064 world.entity(entity_clone).get::<VisibilityClass>().unwrap();
1065 assert_eq!(entity_clone_visibility_class.len(), 1);
1066 }
1067
1068 #[test]
1069 fn view_visibility_lifecycle() {
1070 let mut app = App::new();
1071 app.add_plugins((
1072 TaskPoolPlugin::default(),
1073 bevy_asset::AssetPlugin::default(),
1074 bevy_mesh::MeshPlugin,
1075 bevy_transform::TransformPlugin,
1076 VisibilityPlugin,
1077 ));
1078
1079 #[derive(Resource, Default)]
1080 struct ManualMark(bool);
1081 #[derive(Resource, Default)]
1082 struct ObservedChanged(bool);
1083 app.init_resource::<ManualMark>();
1084 app.init_resource::<ObservedChanged>();
1085
1086 app.add_systems(
1087 PostUpdate,
1088 (
1089 (|mut q: Query<&mut ViewVisibility>, mark: Res<ManualMark>| {
1090 if mark.0 {
1091 for mut v in &mut q {
1092 v.set_visible();
1093 }
1094 }
1095 })
1096 .in_set(VisibilitySystems::CheckVisibility),
1097 (|q: Query<(), Changed<ViewVisibility>>, mut observed: ResMut<ObservedChanged>| {
1098 if !q.is_empty() {
1099 observed.0 = true;
1100 }
1101 })
1102 .after(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
1103 ),
1104 );
1105
1106 let entity = app.world_mut().spawn(ViewVisibility::HIDDEN).id();
1107
1108 app.update();
1110 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1111
1112 app.update();
1114 {
1115 assert!(
1116 !app.world()
1117 .entity(entity)
1118 .get::<ViewVisibility>()
1119 .unwrap()
1120 .get(),
1121 "Frame 1: should be hidden"
1122 );
1123 assert!(
1124 !app.world().resource::<ObservedChanged>().0,
1125 "Frame 1: should not be changed"
1126 );
1127 }
1128
1129 app.world_mut().resource_mut::<ManualMark>().0 = true;
1131 app.update();
1132 {
1133 assert!(
1134 app.world()
1135 .entity(entity)
1136 .get::<ViewVisibility>()
1137 .unwrap()
1138 .get(),
1139 "Frame 2: should be visible"
1140 );
1141 assert!(
1142 app.world().resource::<ObservedChanged>().0,
1143 "Frame 2: should be changed"
1144 );
1145 }
1146
1147 app.world_mut().resource_mut::<ManualMark>().0 = true;
1149 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1150 app.update();
1151 {
1152 assert!(
1153 app.world()
1154 .entity(entity)
1155 .get::<ViewVisibility>()
1156 .unwrap()
1157 .get(),
1158 "Frame 3: should be visible"
1159 );
1160 assert!(
1161 !app.world().resource::<ObservedChanged>().0,
1162 "Frame 3: should NOT be changed"
1163 );
1164 }
1165
1166 app.world_mut().resource_mut::<ManualMark>().0 = false;
1168 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1169 app.update();
1170 {
1171 assert!(
1172 !app.world()
1173 .entity(entity)
1174 .get::<ViewVisibility>()
1175 .unwrap()
1176 .get(),
1177 "Frame 4: should be hidden"
1178 );
1179 assert!(
1180 app.world().resource::<ObservedChanged>().0,
1181 "Frame 4: should be changed"
1182 );
1183 }
1184
1185 app.world_mut().resource_mut::<ManualMark>().0 = false;
1187 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1188 app.update();
1189 {
1190 assert!(
1191 !app.world()
1192 .entity(entity)
1193 .get::<ViewVisibility>()
1194 .unwrap()
1195 .get(),
1196 "Frame 5: should be hidden"
1197 );
1198 assert!(
1199 !app.world().resource::<ObservedChanged>().0,
1200 "Frame 5: should NOT be changed"
1201 );
1202 }
1203 }
1204}