1mod range;
31mod render_layers;
32
33use core::any::TypeId;
34
35use bevy_ecs::entity::EntityHashMap;
36use bevy_ecs::lifecycle::HookContext;
37use bevy_ecs::world::DeferredWorld;
38use bevy_mesh::skinning::{
39 entity_aabb_from_skinned_mesh_bounds, SkinnedMesh, SkinnedMeshInverseBindposes,
40};
41use derive_more::derive::{Deref, DerefMut};
42pub use range::*;
43pub use render_layers::*;
44
45use bevy_app::{Plugin, PostUpdate, ValidateParentHasComponentPlugin};
46use bevy_asset::prelude::AssetChanged;
47use bevy_asset::{AssetEventSystems, Assets};
48use bevy_ecs::{prelude::*, VariantDefaults};
49use bevy_reflect::{std_traits::ReflectDefault, Reflect};
50use bevy_transform::{components::GlobalTransform, TransformSystems};
51use bevy_utils::{Parallel, TypeIdMap};
52use smallvec::SmallVec;
53
54use crate::{
55 camera::Camera,
56 primitives::{Aabb, Frustum, MeshAabb, Sphere},
57 Projection,
58};
59use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, Mesh3d};
60
61#[derive(Component, Default)]
69pub struct NoCpuCulling;
70
71#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default, VariantDefaults)]
81#[reflect(Component, Default, Debug, PartialEq, Clone)]
82#[require(InheritedVisibility, ViewVisibility)]
83pub enum Visibility {
84 #[default]
88 Inherited,
89 Hidden,
91 Visible,
96}
97
98impl Visibility {
99 #[inline]
102 pub fn toggle_inherited_visible(&mut self) {
103 *self = match *self {
104 Visibility::Inherited => Visibility::Visible,
105 Visibility::Visible => Visibility::Inherited,
106 _ => *self,
107 };
108 }
109 #[inline]
112 pub fn toggle_inherited_hidden(&mut self) {
113 *self = match *self {
114 Visibility::Inherited => Visibility::Hidden,
115 Visibility::Hidden => Visibility::Inherited,
116 _ => *self,
117 };
118 }
119 #[inline]
122 pub fn toggle_visible_hidden(&mut self) {
123 *self = match *self {
124 Visibility::Visible => Visibility::Hidden,
125 Visibility::Hidden => Visibility::Visible,
126 _ => *self,
127 };
128 }
129}
130
131impl PartialEq<Visibility> for &Visibility {
133 #[inline]
134 fn eq(&self, other: &Visibility) -> bool {
135 <Visibility as PartialEq<Visibility>>::eq(*self, other)
137 }
138}
139
140impl PartialEq<&Visibility> for Visibility {
142 #[inline]
143 fn eq(&self, other: &&Visibility) -> bool {
144 <Visibility as PartialEq<Visibility>>::eq(self, *other)
146 }
147}
148
149#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
163#[reflect(Component, Default, Debug, PartialEq, Clone)]
164pub struct InheritedVisibility(bool);
165
166impl InheritedVisibility {
167 pub const HIDDEN: Self = Self(false);
169 pub const VISIBLE: Self = Self(true);
171
172 #[inline]
175 pub fn get(self) -> bool {
176 self.0
177 }
178}
179
180#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]
209#[reflect(Component, Default, Clone)]
210#[component(clone_behavior=Ignore)]
211pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);
212
213#[derive(Component, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
225#[reflect(Component, Default, Debug, PartialEq, Clone)]
226pub struct ViewVisibility(
227 u8,
242);
243
244impl ViewVisibility {
245 pub const HIDDEN: Self = Self(0b00);
247
248 pub const VISIBLE: Self = Self(0b11);
254
255 #[inline]
258 pub fn get(self) -> bool {
259 self.0 & 1 != 0
260 }
261
262 #[inline]
264 fn was_visible_now_hidden(self) -> bool {
265 (self.0 & 0b11) == 0b10
267 }
268
269 #[inline]
270 fn update(&mut self) {
271 self.0 = (self.0 & 1) << 1;
274 }
275}
276
277pub trait SetViewVisibility {
278 fn set_visible(&mut self);
288}
289
290impl<'a> SetViewVisibility for Mut<'a, ViewVisibility> {
291 #[inline]
292 fn set_visible(&mut self) {
293 if self.0 & 1 == 0 {
296 if self.0 & 2 != 0 {
297 self.bypass_change_detection().0 |= 1;
300 } else {
301 self.0 |= 1;
304 }
305 }
306 }
307}
308
309#[derive(Debug, Component, Default, Reflect, Clone, PartialEq)]
317#[reflect(Component, Default, Debug)]
318pub struct NoFrustumCulling;
319
320#[derive(Debug, Component, Default, Reflect)]
330#[reflect(Component, Default, Debug)]
331pub struct DynamicSkinnedMeshBounds;
332
333#[derive(Clone, Component, Debug, Reflect)]
343#[reflect(Component, Default, Debug, Clone)]
344pub struct VisibleEntities {
345 #[reflect(ignore, clone)]
346 pub entities: TypeIdMap<Vec<Entity>>,
347}
348
349impl Default for VisibleEntities {
350 fn default() -> VisibleEntities {
351 let mut entities = TypeIdMap::default();
359 entities.insert(TypeId::of::<Mesh3d>(), vec![]);
360 VisibleEntities { entities }
361 }
362}
363
364impl VisibleEntities {
365 pub fn get(&self, type_id: TypeId) -> &[Entity] {
366 match self.entities.get(&type_id) {
367 Some(entities) => &entities[..],
368 None => &[],
369 }
370 }
371
372 pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
373 self.entities.entry(type_id).or_default()
374 }
375
376 pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
377 self.get(type_id).iter()
378 }
379
380 pub fn len(&self, type_id: TypeId) -> usize {
381 self.get(type_id).len()
382 }
383
384 pub fn is_empty(&self, type_id: TypeId) -> bool {
385 self.get(type_id).is_empty()
386 }
387
388 pub fn clear(&mut self, type_id: TypeId) {
389 self.get_mut(type_id).clear();
390 }
391
392 pub fn clear_all(&mut self) {
393 for entities in self.entities.values_mut() {
395 entities.clear();
396 }
397 }
398
399 pub fn push(&mut self, entity: Entity, type_id: TypeId) {
400 self.get_mut(type_id).push(entity);
401 }
402}
403
404#[derive(Component, Clone, Debug, Default, Reflect)]
409#[reflect(Component, Debug, Default, Clone)]
410pub struct VisibleMeshEntities {
411 #[reflect(ignore, clone)]
412 pub entities: Vec<Entity>,
413}
414
415impl VisibleMeshEntities {
416 pub fn shrink(&mut self) {
419 let capacity = self.entities.capacity();
421 let reserved = capacity
422 .checked_div(self.entities.len())
423 .map_or(0, |reserve| {
424 if reserve > 2 {
425 capacity / (reserve / 2)
426 } else {
427 capacity
428 }
429 });
430
431 self.entities.shrink_to(reserved);
432 }
433}
434
435#[derive(Component, Clone, Debug, Default, Reflect)]
436#[reflect(Component, Debug, Default, Clone)]
437pub struct CubemapVisibleEntities {
438 #[reflect(ignore, clone)]
439 data: [VisibleMeshEntities; 6],
440}
441
442impl CubemapVisibleEntities {
443 pub fn get(&self, i: usize) -> &VisibleMeshEntities {
444 &self.data[i]
445 }
446
447 pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities {
448 &mut self.data[i]
449 }
450
451 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleMeshEntities> {
452 self.data.iter()
453 }
454
455 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleMeshEntities> {
456 self.data.iter_mut()
457 }
458}
459
460#[derive(Component, Clone, Debug, Default, Reflect)]
461#[reflect(Component, Default, Clone)]
462pub struct CascadesVisibleEntities {
463 #[reflect(ignore, clone)]
465 pub entities: EntityHashMap<Vec<VisibleMeshEntities>>,
466}
467
468#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
469pub enum VisibilitySystems {
470 CalculateBounds,
473 UpdateFrusta,
475 VisibilityPropagate,
478 CheckVisibility,
487 MarkNewlyHiddenEntitiesInvisible,
491}
492
493pub struct VisibilityPlugin;
494
495impl Plugin for VisibilityPlugin {
496 fn build(&self, app: &mut bevy_app::App) {
497 use VisibilitySystems::*;
498
499 app.add_plugins(ValidateParentHasComponentPlugin::<InheritedVisibility>::default())
500 .register_required_components::<Mesh3d, Visibility>()
501 .register_required_components::<Mesh3d, VisibilityClass>()
502 .register_required_components::<Mesh2d, Visibility>()
503 .register_required_components::<Mesh2d, VisibilityClass>()
504 .configure_sets(
505 PostUpdate,
506 (UpdateFrusta, VisibilityPropagate)
507 .before(CheckVisibility)
508 .after(TransformSystems::Propagate),
509 )
510 .configure_sets(
511 PostUpdate,
512 MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),
513 )
514 .configure_sets(
515 PostUpdate,
516 (CalculateBounds)
517 .before(CheckVisibility)
518 .after(TransformSystems::Propagate)
519 .after(AssetEventSystems)
520 .ambiguous_with(CalculateBounds)
521 .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),
522 )
523 .add_systems(
524 PostUpdate,
525 (
526 (calculate_bounds, update_skinned_mesh_bounds)
527 .chain()
528 .in_set(CalculateBounds),
529 (visibility_propagate_system, reset_view_visibility)
530 .in_set(VisibilityPropagate),
531 (check_visibility_cpu_culling, check_visibility_gpu_culling)
532 .in_set(CheckVisibility),
533 mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),
534 ),
535 );
536 app.world_mut()
537 .register_component_hooks::<Mesh3d>()
538 .on_add(add_visibility_class::<Mesh3d>);
539 app.world_mut()
540 .register_component_hooks::<Mesh2d>()
541 .on_add(add_visibility_class::<Mesh2d>);
542 }
543}
544
545#[derive(Component, Clone, Debug, Default, Reflect)]
551pub struct NoAutoAabb;
552
553pub fn calculate_bounds(
558 mut commands: Commands,
559 meshes: Res<Assets<Mesh>>,
560 new_aabb: Query<
561 (Entity, &Mesh3d),
562 (
563 Without<Aabb>,
564 Without<NoFrustumCulling>,
565 Without<NoAutoAabb>,
566 ),
567 >,
568 mut update_aabb: Query<
569 (&Mesh3d, &mut Aabb),
570 (
571 Or<(AssetChanged<Mesh3d>, Changed<Mesh3d>)>,
572 Without<NoFrustumCulling>,
573 Without<NoAutoAabb>,
574 ),
575 >,
576) {
577 for (entity, mesh_handle) in &new_aabb {
578 if let Some(mesh) = meshes.get(mesh_handle)
579 && let Some(aabb) = mesh.compute_aabb()
580 {
581 commands.entity(entity).try_insert(aabb);
582 }
583 }
584
585 update_aabb
586 .par_iter_mut()
587 .for_each(|(mesh_handle, mut old_aabb)| {
588 if let Some(aabb) = meshes.get(mesh_handle).and_then(MeshAabb::compute_aabb) {
589 *old_aabb = aabb;
590 }
591 });
592}
593
594fn update_skinned_mesh_bounds(
597 inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>,
598 mesh_assets: Res<Assets<Mesh>>,
599 mut mesh_entities: Query<
600 (&mut Aabb, &Mesh3d, &SkinnedMesh, Option<&GlobalTransform>),
601 With<DynamicSkinnedMeshBounds>,
602 >,
603 joint_entities: Query<&GlobalTransform>,
604) {
605 mesh_entities
606 .par_iter_mut()
607 .for_each(|(mut aabb, mesh, skinned_mesh, world_from_entity)| {
608 if let Some(inverse_bindposes_asset) =
609 inverse_bindposes_assets.get(&skinned_mesh.inverse_bindposes)
610 && let Some(mesh_asset) = mesh_assets.get(mesh)
611 && let Ok(skinned_aabb) = entity_aabb_from_skinned_mesh_bounds(
612 &joint_entities,
613 mesh_asset,
614 skinned_mesh,
615 inverse_bindposes_asset,
616 world_from_entity,
617 )
618 {
619 *aabb = skinned_aabb.into();
620 }
621 });
622}
623
624pub fn update_frusta(
628 mut views: Query<
629 (&GlobalTransform, &Projection, &mut Frustum),
630 Or<(Changed<GlobalTransform>, Changed<Projection>)>,
631 >,
632) {
633 for (transform, projection, mut frustum) in &mut views {
634 *frustum = projection.compute_frustum(transform);
635 }
636}
637
638fn visibility_propagate_system(
639 changed: Query<
640 (Entity, &Visibility, Option<&ChildOf>, Option<&Children>),
641 (
642 With<InheritedVisibility>,
643 Or<(Changed<Visibility>, Changed<ChildOf>)>,
644 ),
645 >,
646 mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
647 children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
648 mut removed_child_of: RemovedComponents<ChildOf>,
649) {
650 for (entity, visibility, child_of, children) in &changed {
651 let is_visible = match visibility {
652 Visibility::Visible => true,
653 Visibility::Hidden => false,
654 Visibility::Inherited => child_of
656 .and_then(|c| visibility_query.get(c.parent()).ok())
657 .is_none_or(|(_, x)| x.get()),
658 };
659 let (_, mut inherited_visibility) = visibility_query
660 .get_mut(entity)
661 .expect("With<InheritedVisibility> ensures this query will return a value");
662
663 if inherited_visibility.get() != is_visible {
667 inherited_visibility.0 = is_visible;
668
669 for &child in children.into_iter().flatten() {
671 let _ =
672 propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
673 }
674 }
675 }
676
677 for entity in removed_child_of.read() {
679 let Ok((visibility, mut inherited_visibility)) = visibility_query.get_mut(entity) else {
680 continue;
681 };
682
683 let is_visible = match visibility {
684 Visibility::Visible | Visibility::Inherited => true,
686 Visibility::Hidden => false,
687 };
688
689 if inherited_visibility.get() != is_visible {
690 inherited_visibility.0 = is_visible;
691
692 for &child in children_query.get(entity).ok().into_iter().flatten() {
693 let _ =
694 propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
695 }
696 }
697 }
698}
699
700fn propagate_recursive(
701 parent_is_visible: bool,
702 entity: Entity,
703 visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
704 children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
705 ) -> Result<(), ()> {
708 let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
711
712 let is_visible = match visibility {
713 Visibility::Visible => true,
714 Visibility::Hidden => false,
715 Visibility::Inherited => parent_is_visible,
716 };
717
718 if inherited_visibility.get() != is_visible {
720 inherited_visibility.0 = is_visible;
721
722 for &child in children_query.get(entity).ok().into_iter().flatten() {
724 let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
725 }
726 }
727
728 Ok(())
729}
730
731fn reset_view_visibility(mut reset_query: Query<&mut ViewVisibility, Without<NoCpuCulling>>) {
734 reset_query.par_iter_mut().for_each(|mut view_visibility| {
735 view_visibility.bypass_change_detection().update();
736 });
737}
738
739pub fn check_visibility_cpu_culling(
749 mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,
750 mut view_query: Query<(
751 Entity,
752 &mut VisibleEntities,
753 &Frustum,
754 Option<&RenderLayers>,
755 &Camera,
756 Has<NoCpuCulling>,
757 )>,
758 mut visible_aabb_query: Query<
759 (
760 Entity,
761 &InheritedVisibility,
762 &mut ViewVisibility,
763 Option<&VisibilityClass>,
764 Option<&RenderLayers>,
765 Option<&Aabb>,
766 Option<&Sphere>,
767 &GlobalTransform,
768 Has<NoFrustumCulling>,
769 Has<VisibilityRange>,
770 ),
771 Without<NoCpuCulling>,
772 >,
773 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
774) {
775 let visible_entity_ranges = visible_entity_ranges.as_deref();
776
777 for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling_camera) in
778 &mut view_query
779 {
780 if !camera.is_active {
781 continue;
782 }
783
784 let view_mask = maybe_view_mask.unwrap_or_default();
785
786 visible_aabb_query.par_iter_mut().for_each_init(
787 || thread_queues.borrow_local_mut(),
788 |queue, query_item| {
789 let (
790 entity,
791 inherited_visibility,
792 mut view_visibility,
793 visibility_class,
794 maybe_entity_mask,
795 maybe_model_aabb,
796 maybe_model_sphere,
797 transform,
798 no_frustum_culling,
799 has_visibility_range,
800 ) = query_item;
801
802 if !inherited_visibility.get() {
805 return;
806 }
807
808 let entity_mask = maybe_entity_mask.unwrap_or_default();
809 if !view_mask.intersects(entity_mask) {
810 return;
811 }
812
813 if has_visibility_range
815 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
816 !visible_entity_ranges.entity_is_in_range_of_view(entity, view)
817 })
818 {
819 return;
820 }
821
822 if !no_frustum_culling && !no_cpu_culling_camera {
824 if let Some(model_aabb) = maybe_model_aabb {
825 let world_from_local = transform.affine();
826 let model_sphere = Sphere {
827 center: world_from_local.transform_point3a(model_aabb.center),
828 radius: transform.radius_vec3a(model_aabb.half_extents),
829 };
830 if !frustum.intersects_sphere(&model_sphere, false) {
832 return;
833 }
834 if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
836 return;
837 }
838 } else if let Some(model_sphere) = maybe_model_sphere
839 && !frustum.intersects_sphere(model_sphere, false)
840 {
841 return;
843 }
844 }
845
846 view_visibility.set_visible();
847
848 if let Some(visibility_class) = visibility_class {
853 for visibility_class_id in visibility_class.iter() {
855 queue.entry(*visibility_class_id).or_default().push(entity);
856 }
857 }
858 },
859 );
860
861 visible_entities.clear_all();
862
863 for class_queues in thread_queues.iter_mut() {
865 for (class, entities) in class_queues {
866 visible_entities.get_mut(*class).append(entities);
867 }
868 }
869
870 for visible_entities in visible_entities.entities.values_mut() {
873 visible_entities.sort_unstable();
874 }
875 }
876}
877
878pub fn check_visibility_gpu_culling(
885 mut query: Query<
886 (&mut ViewVisibility, &InheritedVisibility),
887 (
888 With<NoCpuCulling>,
889 Or<(Changed<InheritedVisibility>, Added<NoCpuCulling>)>,
890 ),
891 >,
892) {
893 query
894 .par_iter_mut()
895 .for_each(|(mut view_visibility, inherited_visibility)| {
896 let new_view_visibility = if inherited_visibility.0 {
897 ViewVisibility::VISIBLE
898 } else {
899 ViewVisibility::HIDDEN
900 };
901 view_visibility.set_if_neq(new_view_visibility);
902 });
903}
904
905fn mark_newly_hidden_entities_invisible(
909 mut view_visibilities: Query<&mut ViewVisibility, Without<NoCpuCulling>>,
910) {
911 view_visibilities
912 .par_iter_mut()
913 .for_each(|mut view_visibility| {
914 if view_visibility.as_ref().was_visible_now_hidden() {
915 *view_visibility = ViewVisibility::HIDDEN;
916 }
917 });
918}
919
920pub fn add_visibility_class<C>(
934 mut world: DeferredWorld<'_>,
935 HookContext { entity, .. }: HookContext,
936) where
937 C: 'static,
938{
939 if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
940 visibility_class.push(TypeId::of::<C>());
941 }
942}
943
944#[cfg(test)]
945mod test {
946 use super::*;
947 use bevy_app::prelude::*;
948
949 #[test]
950 fn visibility_propagation() {
951 let mut app = App::new();
952 app.add_systems(Update, visibility_propagate_system);
953
954 let root1 = app.world_mut().spawn(Visibility::Hidden).id();
955 let root1_child1 = app.world_mut().spawn(Visibility::default()).id();
956 let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();
957 let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
958 let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
959
960 app.world_mut()
961 .entity_mut(root1)
962 .add_children(&[root1_child1, root1_child2]);
963 app.world_mut()
964 .entity_mut(root1_child1)
965 .add_children(&[root1_child1_grandchild1]);
966 app.world_mut()
967 .entity_mut(root1_child2)
968 .add_children(&[root1_child2_grandchild1]);
969
970 let root2 = app.world_mut().spawn(Visibility::default()).id();
971 let root2_child1 = app.world_mut().spawn(Visibility::default()).id();
972 let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();
973 let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
974 let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
975
976 app.world_mut()
977 .entity_mut(root2)
978 .add_children(&[root2_child1, root2_child2]);
979 app.world_mut()
980 .entity_mut(root2_child1)
981 .add_children(&[root2_child1_grandchild1]);
982 app.world_mut()
983 .entity_mut(root2_child2)
984 .add_children(&[root2_child2_grandchild1]);
985
986 app.update();
987
988 let is_visible = |e: Entity| {
989 app.world()
990 .entity(e)
991 .get::<InheritedVisibility>()
992 .unwrap()
993 .get()
994 };
995 assert!(
996 !is_visible(root1),
997 "invisibility propagates down tree from root"
998 );
999 assert!(
1000 !is_visible(root1_child1),
1001 "invisibility propagates down tree from root"
1002 );
1003 assert!(
1004 !is_visible(root1_child2),
1005 "invisibility propagates down tree from root"
1006 );
1007 assert!(
1008 !is_visible(root1_child1_grandchild1),
1009 "invisibility propagates down tree from root"
1010 );
1011 assert!(
1012 !is_visible(root1_child2_grandchild1),
1013 "invisibility propagates down tree from root"
1014 );
1015
1016 assert!(
1017 is_visible(root2),
1018 "visibility propagates down tree from root"
1019 );
1020 assert!(
1021 is_visible(root2_child1),
1022 "visibility propagates down tree from root"
1023 );
1024 assert!(
1025 !is_visible(root2_child2),
1026 "visibility propagates down tree from root, but local invisibility is preserved"
1027 );
1028 assert!(
1029 is_visible(root2_child1_grandchild1),
1030 "visibility propagates down tree from root"
1031 );
1032 assert!(
1033 !is_visible(root2_child2_grandchild1),
1034 "child's invisibility propagates down to grandchild"
1035 );
1036 }
1037
1038 #[test]
1039 fn test_visibility_propagation_on_parent_change() {
1040 let mut app = App::new();
1042
1043 app.add_systems(Update, visibility_propagate_system);
1044
1045 let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();
1047 let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();
1048 let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();
1049 let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();
1050
1051 app.world_mut()
1053 .entity_mut(parent1)
1054 .add_children(&[child1, child2]);
1055
1056 app.update();
1058
1059 app.world_mut()
1061 .entity_mut(parent2)
1062 .insert(Visibility::Visible);
1063 app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); app.update();
1068
1069 let is_visible = |e: Entity| {
1070 app.world()
1071 .entity(e)
1072 .get::<InheritedVisibility>()
1073 .unwrap()
1074 .get()
1075 };
1076
1077 assert!(
1080 !is_visible(child1),
1081 "Child1 should inherit visibility from parent"
1082 );
1083
1084 assert!(
1085 is_visible(child2),
1086 "Child2 should inherit visibility from parent"
1087 );
1088 }
1089
1090 #[test]
1091 fn test_visibility_propagation_on_parent_removed() {
1092 let mut app = App::new();
1094
1095 app.add_systems(Update, visibility_propagate_system);
1096
1097 let parent = app.world_mut().spawn((Visibility::Hidden,)).id();
1099 let child = app.world_mut().spawn((Visibility::Inherited,)).id();
1100
1101 app.world_mut().entity_mut(parent).add_children(&[child]);
1103
1104 app.update();
1106
1107 let is_visible = |app: &App, e: Entity| {
1108 app.world()
1109 .entity(e)
1110 .get::<InheritedVisibility>()
1111 .unwrap()
1112 .get()
1113 };
1114
1115 assert!(
1116 !is_visible(&app, child),
1117 "Child should inherit visibility from parent"
1118 );
1119
1120 app.world_mut().entity_mut(child).remove::<ChildOf>(); app.update();
1125
1126 assert!(is_visible(&app, child), "Child should have become visible");
1129 }
1130
1131 #[test]
1132 fn visibility_propagation_unconditional_visible() {
1133 use Visibility::{Hidden, Inherited, Visible};
1134
1135 let mut app = App::new();
1136 app.add_systems(Update, visibility_propagate_system);
1137
1138 let root1 = app.world_mut().spawn(Visible).id();
1139 let root1_child1 = app.world_mut().spawn(Inherited).id();
1140 let root1_child2 = app.world_mut().spawn(Hidden).id();
1141 let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();
1142 let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();
1143
1144 let root2 = app.world_mut().spawn(Inherited).id();
1145 let root3 = app.world_mut().spawn(Hidden).id();
1146
1147 app.world_mut()
1148 .entity_mut(root1)
1149 .add_children(&[root1_child1, root1_child2]);
1150 app.world_mut()
1151 .entity_mut(root1_child1)
1152 .add_children(&[root1_child1_grandchild1]);
1153 app.world_mut()
1154 .entity_mut(root1_child2)
1155 .add_children(&[root1_child2_grandchild1]);
1156
1157 app.update();
1158
1159 let is_visible = |e: Entity| {
1160 app.world()
1161 .entity(e)
1162 .get::<InheritedVisibility>()
1163 .unwrap()
1164 .get()
1165 };
1166 assert!(
1167 is_visible(root1),
1168 "an unconditionally visible root is visible"
1169 );
1170 assert!(
1171 is_visible(root1_child1),
1172 "an inheriting child of an unconditionally visible parent is visible"
1173 );
1174 assert!(
1175 !is_visible(root1_child2),
1176 "a hidden child on an unconditionally visible parent is hidden"
1177 );
1178 assert!(
1179 is_visible(root1_child1_grandchild1),
1180 "an unconditionally visible child of an inheriting parent is visible"
1181 );
1182 assert!(
1183 is_visible(root1_child2_grandchild1),
1184 "an unconditionally visible child of a hidden parent is visible"
1185 );
1186 assert!(is_visible(root2), "an inheriting root is visible");
1187 assert!(!is_visible(root3), "a hidden root is hidden");
1188 }
1189
1190 #[test]
1191 fn visibility_propagation_change_detection() {
1192 let mut world = World::new();
1193 let mut schedule = Schedule::default();
1194 schedule.add_systems(visibility_propagate_system);
1195
1196 let id1 = world.spawn(Visibility::default()).id();
1199
1200 let id2 = world.spawn(Visibility::default()).id();
1201 world.entity_mut(id1).add_children(&[id2]);
1202
1203 let id3 = world.spawn(Visibility::Hidden).id();
1204 world.entity_mut(id2).add_children(&[id3]);
1205
1206 let id4 = world.spawn(Visibility::default()).id();
1207 world.entity_mut(id3).add_children(&[id4]);
1208
1209 schedule.run(&mut world);
1213 world.clear_trackers();
1214
1215 let mut q = world.query::<Ref<InheritedVisibility>>();
1216
1217 assert!(!q.get(&world, id1).unwrap().is_changed());
1218 assert!(!q.get(&world, id2).unwrap().is_changed());
1219 assert!(!q.get(&world, id3).unwrap().is_changed());
1220 assert!(!q.get(&world, id4).unwrap().is_changed());
1221
1222 world.clear_trackers();
1223 world.entity_mut(id1).insert(Visibility::Hidden);
1224 schedule.run(&mut world);
1225
1226 assert!(q.get(&world, id1).unwrap().is_changed());
1227 assert!(q.get(&world, id2).unwrap().is_changed());
1228 assert!(!q.get(&world, id3).unwrap().is_changed());
1229 assert!(!q.get(&world, id4).unwrap().is_changed());
1230
1231 world.clear_trackers();
1232 schedule.run(&mut world);
1233
1234 assert!(!q.get(&world, id1).unwrap().is_changed());
1235 assert!(!q.get(&world, id2).unwrap().is_changed());
1236 assert!(!q.get(&world, id3).unwrap().is_changed());
1237 assert!(!q.get(&world, id4).unwrap().is_changed());
1238
1239 world.clear_trackers();
1240 world.entity_mut(id3).insert(Visibility::Inherited);
1241 schedule.run(&mut world);
1242
1243 assert!(!q.get(&world, id1).unwrap().is_changed());
1244 assert!(!q.get(&world, id2).unwrap().is_changed());
1245 assert!(!q.get(&world, id3).unwrap().is_changed());
1246 assert!(!q.get(&world, id4).unwrap().is_changed());
1247
1248 world.clear_trackers();
1249 world.entity_mut(id2).insert(Visibility::Visible);
1250 schedule.run(&mut world);
1251
1252 assert!(!q.get(&world, id1).unwrap().is_changed());
1253 assert!(q.get(&world, id2).unwrap().is_changed());
1254 assert!(q.get(&world, id3).unwrap().is_changed());
1255 assert!(q.get(&world, id4).unwrap().is_changed());
1256
1257 world.clear_trackers();
1258 schedule.run(&mut world);
1259
1260 assert!(!q.get(&world, id1).unwrap().is_changed());
1261 assert!(!q.get(&world, id2).unwrap().is_changed());
1262 assert!(!q.get(&world, id3).unwrap().is_changed());
1263 assert!(!q.get(&world, id4).unwrap().is_changed());
1264 }
1265
1266 #[test]
1267 fn visibility_propagation_with_invalid_parent() {
1268 let mut world = World::new();
1269 let mut schedule = Schedule::default();
1270 schedule.add_systems(visibility_propagate_system);
1271
1272 let parent = world.spawn(()).id();
1273 let child = world.spawn(Visibility::default()).id();
1274 world.entity_mut(parent).add_children(&[child]);
1275
1276 schedule.run(&mut world);
1277 world.clear_trackers();
1278
1279 let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
1280 assert!(child_visible);
1282 }
1283
1284 #[test]
1285 fn ensure_visibility_enum_size() {
1286 assert_eq!(1, size_of::<Visibility>());
1287 assert_eq!(1, size_of::<Option<Visibility>>());
1288 }
1289
1290 #[derive(Component, Default, Clone, Reflect)]
1291 #[require(VisibilityClass)]
1292 #[reflect(Component, Default, Clone)]
1293 #[component(on_add = add_visibility_class::<Self>)]
1294 struct TestVisibilityClassHook;
1295
1296 #[test]
1297 fn test_add_visibility_class_hook() {
1298 let mut world = World::new();
1299 let entity = world.spawn(TestVisibilityClassHook).id();
1300 let entity_clone = world.spawn_empty().id();
1301 world
1302 .entity_mut(entity)
1303 .clone_with_opt_out(entity_clone, |_| {});
1304
1305 let entity_visibility_class = world.entity(entity).get::<VisibilityClass>().unwrap();
1306 assert_eq!(entity_visibility_class.len(), 1);
1307
1308 let entity_clone_visibility_class =
1309 world.entity(entity_clone).get::<VisibilityClass>().unwrap();
1310 assert_eq!(entity_clone_visibility_class.len(), 1);
1311 }
1312
1313 #[test]
1314 fn view_visibility_lifecycle() {
1315 let mut app = App::new();
1316 app.add_plugins((
1317 TaskPoolPlugin::default(),
1318 bevy_asset::AssetPlugin::default(),
1319 bevy_mesh::MeshPlugin,
1320 bevy_transform::TransformPlugin,
1321 VisibilityPlugin,
1322 ));
1323
1324 #[derive(Resource, Default)]
1325 struct ManualMark(bool);
1326 #[derive(Resource, Default)]
1327 struct ObservedChanged(bool);
1328 app.init_resource::<ManualMark>();
1329 app.init_resource::<ObservedChanged>();
1330
1331 app.add_systems(
1332 PostUpdate,
1333 (
1334 (|mut q: Query<&mut ViewVisibility>, mark: Res<ManualMark>| {
1335 if mark.0 {
1336 for mut v in &mut q {
1337 v.set_visible();
1338 }
1339 }
1340 })
1341 .in_set(VisibilitySystems::CheckVisibility),
1342 (|q: Query<(), Changed<ViewVisibility>>, mut observed: ResMut<ObservedChanged>| {
1343 if !q.is_empty() {
1344 observed.0 = true;
1345 }
1346 })
1347 .after(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
1348 ),
1349 );
1350
1351 let entity = app.world_mut().spawn(ViewVisibility::HIDDEN).id();
1352
1353 app.update();
1355 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1356
1357 app.update();
1359 {
1360 assert!(
1361 !app.world()
1362 .entity(entity)
1363 .get::<ViewVisibility>()
1364 .unwrap()
1365 .get(),
1366 "Frame 1: should be hidden"
1367 );
1368 assert!(
1369 !app.world().resource::<ObservedChanged>().0,
1370 "Frame 1: should not be changed"
1371 );
1372 }
1373
1374 app.world_mut().resource_mut::<ManualMark>().0 = true;
1376 app.update();
1377 {
1378 assert!(
1379 app.world()
1380 .entity(entity)
1381 .get::<ViewVisibility>()
1382 .unwrap()
1383 .get(),
1384 "Frame 2: should be visible"
1385 );
1386 assert!(
1387 app.world().resource::<ObservedChanged>().0,
1388 "Frame 2: should be changed"
1389 );
1390 }
1391
1392 app.world_mut().resource_mut::<ManualMark>().0 = true;
1394 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1395 app.update();
1396 {
1397 assert!(
1398 app.world()
1399 .entity(entity)
1400 .get::<ViewVisibility>()
1401 .unwrap()
1402 .get(),
1403 "Frame 3: should be visible"
1404 );
1405 assert!(
1406 !app.world().resource::<ObservedChanged>().0,
1407 "Frame 3: should NOT be changed"
1408 );
1409 }
1410
1411 app.world_mut().resource_mut::<ManualMark>().0 = false;
1413 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1414 app.update();
1415 {
1416 assert!(
1417 !app.world()
1418 .entity(entity)
1419 .get::<ViewVisibility>()
1420 .unwrap()
1421 .get(),
1422 "Frame 4: should be hidden"
1423 );
1424 assert!(
1425 app.world().resource::<ObservedChanged>().0,
1426 "Frame 4: should be changed"
1427 );
1428 }
1429
1430 app.world_mut().resource_mut::<ManualMark>().0 = false;
1432 app.world_mut().resource_mut::<ObservedChanged>().0 = false;
1433 app.update();
1434 {
1435 assert!(
1436 !app.world()
1437 .entity(entity)
1438 .get::<ViewVisibility>()
1439 .unwrap()
1440 .get(),
1441 "Frame 5: should be hidden"
1442 );
1443 assert!(
1444 !app.world().resource::<ObservedChanged>().0,
1445 "Frame 5: should NOT be changed"
1446 );
1447 }
1448 }
1449}