Path: blob/main/crates/bevy_camera/src/visibility/mod.rs
6849 views
mod range;1mod render_layers;23use core::any::TypeId;45use bevy_ecs::entity::{EntityHashMap, EntityHashSet};6use bevy_ecs::lifecycle::HookContext;7use bevy_ecs::world::DeferredWorld;8use derive_more::derive::{Deref, DerefMut};9pub use range::*;10pub use render_layers::*;1112use bevy_app::{Plugin, PostUpdate};13use bevy_asset::Assets;14use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*};15use bevy_reflect::{std_traits::ReflectDefault, Reflect};16use bevy_transform::{components::GlobalTransform, TransformSystems};17use bevy_utils::{Parallel, TypeIdMap};18use smallvec::SmallVec;1920use crate::{21camera::Camera,22primitives::{Aabb, Frustum, MeshAabb, Sphere},23Projection,24};25use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, Mesh3d};2627#[derive(Component, Default)]28pub struct NoCpuCulling;2930/// User indication of whether an entity is visible. Propagates down the entity hierarchy.31///32/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who33/// are set to [`Inherited`](Self::Inherited) will also be hidden.34///35/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and36/// `Visibility` to set the values of each entity's [`InheritedVisibility`] component.37#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]38#[reflect(Component, Default, Debug, PartialEq, Clone)]39#[require(InheritedVisibility, ViewVisibility)]40pub enum Visibility {41/// An entity with `Visibility::Inherited` will inherit the Visibility of its [`ChildOf`] target.42///43/// A root-level entity that is set to `Inherited` will be visible.44#[default]45Inherited,46/// An entity with `Visibility::Hidden` will be unconditionally hidden.47Hidden,48/// An entity with `Visibility::Visible` will be unconditionally visible.49///50/// Note that an entity with `Visibility::Visible` will be visible regardless of whether the51/// [`ChildOf`] target entity is hidden.52Visible,53}5455impl Visibility {56/// Toggles between `Visibility::Inherited` and `Visibility::Visible`.57/// If the value is `Visibility::Hidden`, it remains unaffected.58#[inline]59pub fn toggle_inherited_visible(&mut self) {60*self = match *self {61Visibility::Inherited => Visibility::Visible,62Visibility::Visible => Visibility::Inherited,63_ => *self,64};65}66/// Toggles between `Visibility::Inherited` and `Visibility::Hidden`.67/// If the value is `Visibility::Visible`, it remains unaffected.68#[inline]69pub fn toggle_inherited_hidden(&mut self) {70*self = match *self {71Visibility::Inherited => Visibility::Hidden,72Visibility::Hidden => Visibility::Inherited,73_ => *self,74};75}76/// Toggles between `Visibility::Visible` and `Visibility::Hidden`.77/// If the value is `Visibility::Inherited`, it remains unaffected.78#[inline]79pub fn toggle_visible_hidden(&mut self) {80*self = match *self {81Visibility::Visible => Visibility::Hidden,82Visibility::Hidden => Visibility::Visible,83_ => *self,84};85}86}8788// Allows `&Visibility == Visibility`89impl PartialEq<Visibility> for &Visibility {90#[inline]91fn eq(&self, other: &Visibility) -> bool {92// Use the base Visibility == Visibility implementation.93<Visibility as PartialEq<Visibility>>::eq(*self, other)94}95}9697// Allows `Visibility == &Visibility`98impl PartialEq<&Visibility> for Visibility {99#[inline]100fn eq(&self, other: &&Visibility) -> bool {101// Use the base Visibility == Visibility implementation.102<Visibility as PartialEq<Visibility>>::eq(self, *other)103}104}105106/// Whether or not an entity is visible in the hierarchy.107/// This will not be accurate until [`VisibilityPropagate`] runs in the [`PostUpdate`] schedule.108///109/// If this is false, then [`ViewVisibility`] should also be false.110///111/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate112#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]113#[reflect(Component, Default, Debug, PartialEq, Clone)]114#[component(on_insert = validate_parent_has_component::<Self>)]115pub struct InheritedVisibility(bool);116117impl InheritedVisibility {118/// An entity that is invisible in the hierarchy.119pub const HIDDEN: Self = Self(false);120/// An entity that is visible in the hierarchy.121pub const VISIBLE: Self = Self(true);122123/// Returns `true` if the entity is visible in the hierarchy.124/// Otherwise, returns `false`.125#[inline]126pub fn get(self) -> bool {127self.0128}129}130131/// A bucket into which we group entities for the purposes of visibility.132///133/// Bevy's various rendering subsystems (3D, 2D, etc.) want to be able to134/// quickly winnow the set of entities to only those that the subsystem is135/// tasked with rendering, to avoid spending time examining irrelevant entities.136/// At the same time, Bevy wants the [`check_visibility`] system to determine137/// all entities' visibilities at the same time, regardless of what rendering138/// subsystem is responsible for drawing them. Additionally, your application139/// may want to add more types of renderable objects that Bevy determines140/// visibility for just as it does for Bevy's built-in objects.141///142/// The solution to this problem is *visibility classes*. A visibility class is143/// a type, typically the type of a component, that represents the subsystem144/// that renders it: for example, `Mesh3d`, `Mesh2d`, and `Sprite`. The145/// [`VisibilityClass`] component stores the visibility class or classes that146/// the entity belongs to. (Generally, an object will belong to only one147/// visibility class, but in rare cases it may belong to multiple.)148///149/// When adding a new renderable component, you'll typically want to write an150/// add-component hook that adds the type ID of that component to the151/// [`VisibilityClass`] array. See `custom_phase_item` for an example.152///153/// `VisibilityClass` is automatically added by a hook on the `Mesh3d` and154/// `Mesh2d` components. To avoid duplicating the `VisibilityClass` and155/// causing issues when cloning, we use `#[component(clone_behavior=Ignore)]`156//157// Note: This can't be a `ComponentId` because the visibility classes are copied158// into the render world, and component IDs are per-world.159#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]160#[reflect(Component, Default, Clone)]161#[component(clone_behavior=Ignore)]162pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);163164/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.165///166/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].167/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].168/// Because of this, values of this type will be marked as changed every frame, even when they do not change.169///170/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.171///172/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate173/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility174#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]175#[reflect(Component, Default, Debug, PartialEq, Clone)]176pub struct ViewVisibility(bool);177178impl ViewVisibility {179/// An entity that cannot be seen from any views.180pub const HIDDEN: Self = Self(false);181182/// Returns `true` if the entity is visible in any view.183/// Otherwise, returns `false`.184#[inline]185pub fn get(self) -> bool {186self.0187}188189/// Sets the visibility to `true`. This should not be considered reversible for a given frame,190/// as this component tracks whether or not the entity visible in _any_ view.191///192/// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set193/// to the proper value in [`CheckVisibility`].194///195/// You should only manually set this if you are defining a custom visibility system,196/// in which case the system should be placed in the [`CheckVisibility`] set.197/// For normal user-defined entity visibility, see [`Visibility`].198///199/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate200/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility201#[inline]202pub fn set(&mut self) {203self.0 = true;204}205}206207/// Use this component to opt-out of built-in frustum culling for entities, see208/// [`Frustum`].209///210/// It can be used for example:211/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations,212/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`]213/// to appear in the reflection of a [`Mesh`] within.214#[derive(Debug, Component, Default, Reflect)]215#[reflect(Component, Default, Debug)]216pub struct NoFrustumCulling;217218/// Collection of entities visible from the current view.219///220/// This component contains all entities which are visible from the currently221/// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`]222/// system set. Renderers can use the equivalent `RenderVisibleEntities` to optimize rendering of223/// a particular view, to prevent drawing items not visible from that view.224///225/// This component is intended to be attached to the same entity as the [`Camera`] and226/// the [`Frustum`] defining the view.227#[derive(Clone, Component, Default, Debug, Reflect)]228#[reflect(Component, Default, Debug, Clone)]229pub struct VisibleEntities {230#[reflect(ignore, clone)]231pub entities: TypeIdMap<Vec<Entity>>,232}233234impl VisibleEntities {235pub fn get(&self, type_id: TypeId) -> &[Entity] {236match self.entities.get(&type_id) {237Some(entities) => &entities[..],238None => &[],239}240}241242pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {243self.entities.entry(type_id).or_default()244}245246pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {247self.get(type_id).iter()248}249250pub fn len(&self, type_id: TypeId) -> usize {251self.get(type_id).len()252}253254pub fn is_empty(&self, type_id: TypeId) -> bool {255self.get(type_id).is_empty()256}257258pub fn clear(&mut self, type_id: TypeId) {259self.get_mut(type_id).clear();260}261262pub fn clear_all(&mut self) {263// Don't just nuke the hash table; we want to reuse allocations.264for entities in self.entities.values_mut() {265entities.clear();266}267}268269pub fn push(&mut self, entity: Entity, type_id: TypeId) {270self.get_mut(type_id).push(entity);271}272}273274/// Collection of mesh entities visible for 3D lighting.275///276/// This component contains all mesh entities visible from the current light view.277/// The collection is updated automatically by `bevy_pbr::SimulationLightSystems`.278#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]279#[reflect(Component, Debug, Default, Clone)]280pub struct VisibleMeshEntities {281#[reflect(ignore, clone)]282pub entities: Vec<Entity>,283}284285#[derive(Component, Clone, Debug, Default, Reflect)]286#[reflect(Component, Debug, Default, Clone)]287pub struct CubemapVisibleEntities {288#[reflect(ignore, clone)]289data: [VisibleMeshEntities; 6],290}291292impl CubemapVisibleEntities {293pub fn get(&self, i: usize) -> &VisibleMeshEntities {294&self.data[i]295}296297pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities {298&mut self.data[i]299}300301pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleMeshEntities> {302self.data.iter()303}304305pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleMeshEntities> {306self.data.iter_mut()307}308}309310#[derive(Component, Clone, Debug, Default, Reflect)]311#[reflect(Component, Default, Clone)]312pub struct CascadesVisibleEntities {313/// Map of view entity to the visible entities for each cascade frustum.314#[reflect(ignore, clone)]315pub entities: EntityHashMap<Vec<VisibleMeshEntities>>,316}317318#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]319pub enum VisibilitySystems {320/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,321/// calculating and inserting an [`Aabb`] to relevant entities.322CalculateBounds,323/// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).324UpdateFrusta,325/// Label for the system propagating the [`InheritedVisibility`] in a326/// [`ChildOf`] / [`Children`] hierarchy.327VisibilityPropagate,328/// Label for the [`check_visibility`] system updating [`ViewVisibility`]329/// of each entity and the [`VisibleEntities`] of each view.\330///331/// System order ambiguities between systems in this set are ignored:332/// the order of systems within this set is irrelevant, as [`check_visibility`]333/// assumes that its operations are irreversible during the frame.334CheckVisibility,335/// Label for the `mark_newly_hidden_entities_invisible` system, which sets336/// [`ViewVisibility`] to [`ViewVisibility::HIDDEN`] for entities that no337/// view has marked as visible.338MarkNewlyHiddenEntitiesInvisible,339}340341pub struct VisibilityPlugin;342343impl Plugin for VisibilityPlugin {344fn build(&self, app: &mut bevy_app::App) {345use VisibilitySystems::*;346347app.register_required_components::<Mesh3d, Visibility>()348.register_required_components::<Mesh3d, VisibilityClass>()349.register_required_components::<Mesh2d, Visibility>()350.register_required_components::<Mesh2d, VisibilityClass>()351.configure_sets(352PostUpdate,353(354CalculateBounds355.ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),356UpdateFrusta,357VisibilityPropagate,358)359.before(CheckVisibility)360.after(TransformSystems::Propagate),361)362.configure_sets(363PostUpdate,364MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),365)366.init_resource::<PreviousVisibleEntities>()367.add_systems(368PostUpdate,369(370calculate_bounds.in_set(CalculateBounds),371(visibility_propagate_system, reset_view_visibility)372.in_set(VisibilityPropagate),373check_visibility.in_set(CheckVisibility),374mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),375),376);377app.world_mut()378.register_component_hooks::<Mesh3d>()379.on_add(add_visibility_class::<Mesh3d>);380app.world_mut()381.register_component_hooks::<Mesh2d>()382.on_add(add_visibility_class::<Mesh2d>);383}384}385386/// Computes and adds an [`Aabb`] component to entities with a387/// [`Mesh3d`] component and without a [`NoFrustumCulling`] component.388///389/// This system is used in system set [`VisibilitySystems::CalculateBounds`].390pub fn calculate_bounds(391mut commands: Commands,392meshes: Res<Assets<Mesh>>,393without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,394) {395for (entity, mesh_handle) in &without_aabb {396if let Some(mesh) = meshes.get(mesh_handle)397&& let Some(aabb) = mesh.compute_aabb()398{399commands.entity(entity).try_insert(aabb);400}401}402}403404/// Updates [`Frustum`].405///406/// This system is used in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).407pub fn update_frusta(408mut views: Query<409(&GlobalTransform, &Projection, &mut Frustum),410Or<(Changed<GlobalTransform>, Changed<Projection>)>,411>,412) {413for (transform, projection, mut frustum) in &mut views {414*frustum = projection.compute_frustum(transform);415}416}417418fn visibility_propagate_system(419changed: Query<420(Entity, &Visibility, Option<&ChildOf>, Option<&Children>),421(422With<InheritedVisibility>,423Or<(Changed<Visibility>, Changed<ChildOf>)>,424),425>,426mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,427children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,428) {429for (entity, visibility, child_of, children) in &changed {430let is_visible = match visibility {431Visibility::Visible => true,432Visibility::Hidden => false,433// fall back to true if no parent is found or parent lacks components434Visibility::Inherited => child_of435.and_then(|c| visibility_query.get(c.parent()).ok())436.is_none_or(|(_, x)| x.get()),437};438let (_, mut inherited_visibility) = visibility_query439.get_mut(entity)440.expect("With<InheritedVisibility> ensures this query will return a value");441442// Only update the visibility if it has changed.443// This will also prevent the visibility from propagating multiple times in the same frame444// if this entity's visibility has been updated recursively by its parent.445if inherited_visibility.get() != is_visible {446inherited_visibility.0 = is_visible;447448// Recursively update the visibility of each child.449for &child in children.into_iter().flatten() {450let _ =451propagate_recursive(is_visible, child, &mut visibility_query, &children_query);452}453}454}455}456457fn propagate_recursive(458parent_is_visible: bool,459entity: Entity,460visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,461children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,462// BLOCKED: https://github.com/rust-lang/rust/issues/31436463// We use a result here to use the `?` operator. Ideally we'd use a try block instead464) -> Result<(), ()> {465// Get the visibility components for the current entity.466// If the entity does not have the required components, just return early.467let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;468469let is_visible = match visibility {470Visibility::Visible => true,471Visibility::Hidden => false,472Visibility::Inherited => parent_is_visible,473};474475// Only update the visibility if it has changed.476if inherited_visibility.get() != is_visible {477inherited_visibility.0 = is_visible;478479// Recursively update the visibility of each child.480for &child in children_query.get(entity).ok().into_iter().flatten() {481let _ = propagate_recursive(is_visible, child, visibility_query, children_query);482}483}484485Ok(())486}487488/// Stores all entities that were visible in the previous frame.489///490/// As systems that check visibility judge entities visible, they remove them491/// from this set. Afterward, the `mark_newly_hidden_entities_invisible` system492/// runs and marks every mesh still remaining in this set as hidden.493#[derive(Resource, Default, Deref, DerefMut)]494pub struct PreviousVisibleEntities(EntityHashSet);495496/// Resets the view visibility of every entity.497/// Entities that are visible will be marked as such later this frame498/// by a [`VisibilitySystems::CheckVisibility`] system.499fn reset_view_visibility(500mut query: Query<(Entity, &ViewVisibility)>,501mut previous_visible_entities: ResMut<PreviousVisibleEntities>,502) {503previous_visible_entities.clear();504505query.iter_mut().for_each(|(entity, view_visibility)| {506// Record the entities that were previously visible.507if view_visibility.get() {508previous_visible_entities.insert(entity);509}510});511}512513/// System updating the visibility of entities each frame.514///515/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each516/// frame, it updates the [`ViewVisibility`] of all entities, and for each view517/// also compute the [`VisibleEntities`] for that view.518///519/// To ensure that an entity is checked for visibility, make sure that it has a520/// [`VisibilityClass`] component and that that component is nonempty.521pub fn check_visibility(522mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,523mut view_query: Query<(524Entity,525&mut VisibleEntities,526&Frustum,527Option<&RenderLayers>,528&Camera,529Has<NoCpuCulling>,530)>,531mut visible_aabb_query: Query<(532Entity,533&InheritedVisibility,534&mut ViewVisibility,535&VisibilityClass,536Option<&RenderLayers>,537Option<&Aabb>,538&GlobalTransform,539Has<NoFrustumCulling>,540Has<VisibilityRange>,541)>,542visible_entity_ranges: Option<Res<VisibleEntityRanges>>,543mut previous_visible_entities: ResMut<PreviousVisibleEntities>,544) {545let visible_entity_ranges = visible_entity_ranges.as_deref();546547for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in548&mut view_query549{550if !camera.is_active {551continue;552}553554let view_mask = maybe_view_mask.unwrap_or_default();555556visible_aabb_query.par_iter_mut().for_each_init(557|| thread_queues.borrow_local_mut(),558|queue, query_item| {559let (560entity,561inherited_visibility,562mut view_visibility,563visibility_class,564maybe_entity_mask,565maybe_model_aabb,566transform,567no_frustum_culling,568has_visibility_range,569) = query_item;570571// Skip computing visibility for entities that are configured to be hidden.572// ViewVisibility has already been reset in `reset_view_visibility`.573if !inherited_visibility.get() {574return;575}576577let entity_mask = maybe_entity_mask.unwrap_or_default();578if !view_mask.intersects(entity_mask) {579return;580}581582// If outside of the visibility range, cull.583if has_visibility_range584&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {585!visible_entity_ranges.entity_is_in_range_of_view(entity, view)586})587{588return;589}590591// If we have an aabb, do frustum culling592if !no_frustum_culling593&& !no_cpu_culling594&& let Some(model_aabb) = maybe_model_aabb595{596let world_from_local = transform.affine();597let model_sphere = Sphere {598center: world_from_local.transform_point3a(model_aabb.center),599radius: transform.radius_vec3a(model_aabb.half_extents),600};601// Do quick sphere-based frustum culling602if !frustum.intersects_sphere(&model_sphere, false) {603return;604}605// Do aabb-based frustum culling606if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {607return;608}609}610611// Make sure we don't trigger changed notifications612// unnecessarily by checking whether the flag is set before613// setting it.614if !**view_visibility {615view_visibility.set();616}617618// Add the entity to the queue for all visibility classes the619// entity is in.620for visibility_class_id in visibility_class.iter() {621queue.entry(*visibility_class_id).or_default().push(entity);622}623},624);625626visible_entities.clear_all();627628// Drain all the thread queues into the `visible_entities` list.629for class_queues in thread_queues.iter_mut() {630for (class, entities) in class_queues {631let visible_entities_for_class = visible_entities.get_mut(*class);632for entity in entities.drain(..) {633// As we mark entities as visible, we remove them from the634// `previous_visible_entities` list. At the end, all of the635// entities remaining in `previous_visible_entities` will be636// entities that were visible last frame but are no longer637// visible this frame.638previous_visible_entities.remove(&entity);639640visible_entities_for_class.push(entity);641}642}643}644}645}646647/// Marks any entities that weren't judged visible this frame as invisible.648///649/// As visibility-determining systems run, they remove entities that they judge650/// visible from [`PreviousVisibleEntities`]. At the end of visibility651/// determination, all entities that remain in [`PreviousVisibleEntities`] must652/// be invisible. This system goes through those entities and marks them newly653/// invisible (which sets the change flag for them).654fn mark_newly_hidden_entities_invisible(655mut view_visibilities: Query<&mut ViewVisibility>,656mut previous_visible_entities: ResMut<PreviousVisibleEntities>,657) {658// Whatever previous visible entities are left are entities that were659// visible last frame but just became invisible.660for entity in previous_visible_entities.drain() {661if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) {662*view_visibility = ViewVisibility::HIDDEN;663}664}665}666667/// A generic component add hook that automatically adds the appropriate668/// [`VisibilityClass`] to an entity.669///670/// This can be handy when creating custom renderable components. To use this671/// hook, add it to your renderable component like this:672///673/// ```ignore674/// #[derive(Component)]675/// #[component(on_add = add_visibility_class::<MyComponent>)]676/// struct MyComponent {677/// ...678/// }679/// ```680pub fn add_visibility_class<C>(681mut world: DeferredWorld<'_>,682HookContext { entity, .. }: HookContext,683) where684C: 'static,685{686if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {687visibility_class.push(TypeId::of::<C>());688}689}690691#[cfg(test)]692mod test {693use super::*;694use bevy_app::prelude::*;695696#[test]697fn visibility_propagation() {698let mut app = App::new();699app.add_systems(Update, visibility_propagate_system);700701let root1 = app.world_mut().spawn(Visibility::Hidden).id();702let root1_child1 = app.world_mut().spawn(Visibility::default()).id();703let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();704let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();705let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();706707app.world_mut()708.entity_mut(root1)709.add_children(&[root1_child1, root1_child2]);710app.world_mut()711.entity_mut(root1_child1)712.add_children(&[root1_child1_grandchild1]);713app.world_mut()714.entity_mut(root1_child2)715.add_children(&[root1_child2_grandchild1]);716717let root2 = app.world_mut().spawn(Visibility::default()).id();718let root2_child1 = app.world_mut().spawn(Visibility::default()).id();719let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();720let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();721let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();722723app.world_mut()724.entity_mut(root2)725.add_children(&[root2_child1, root2_child2]);726app.world_mut()727.entity_mut(root2_child1)728.add_children(&[root2_child1_grandchild1]);729app.world_mut()730.entity_mut(root2_child2)731.add_children(&[root2_child2_grandchild1]);732733app.update();734735let is_visible = |e: Entity| {736app.world()737.entity(e)738.get::<InheritedVisibility>()739.unwrap()740.get()741};742assert!(743!is_visible(root1),744"invisibility propagates down tree from root"745);746assert!(747!is_visible(root1_child1),748"invisibility propagates down tree from root"749);750assert!(751!is_visible(root1_child2),752"invisibility propagates down tree from root"753);754assert!(755!is_visible(root1_child1_grandchild1),756"invisibility propagates down tree from root"757);758assert!(759!is_visible(root1_child2_grandchild1),760"invisibility propagates down tree from root"761);762763assert!(764is_visible(root2),765"visibility propagates down tree from root"766);767assert!(768is_visible(root2_child1),769"visibility propagates down tree from root"770);771assert!(772!is_visible(root2_child2),773"visibility propagates down tree from root, but local invisibility is preserved"774);775assert!(776is_visible(root2_child1_grandchild1),777"visibility propagates down tree from root"778);779assert!(780!is_visible(root2_child2_grandchild1),781"child's invisibility propagates down to grandchild"782);783}784785#[test]786fn test_visibility_propagation_on_parent_change() {787// Setup the world and schedule788let mut app = App::new();789790app.add_systems(Update, visibility_propagate_system);791792// Create entities with visibility and hierarchy793let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();794let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();795let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();796let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();797798// Build hierarchy799app.world_mut()800.entity_mut(parent1)801.add_children(&[child1, child2]);802803// Run the system initially to set up visibility804app.update();805806// Change parent visibility to Hidden807app.world_mut()808.entity_mut(parent2)809.insert(Visibility::Visible);810// Simulate a change in the parent component811app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent812813// Run the system again to propagate changes814app.update();815816let is_visible = |e: Entity| {817app.world()818.entity(e)819.get::<InheritedVisibility>()820.unwrap()821.get()822};823824// Retrieve and assert visibility825826assert!(827!is_visible(child1),828"Child1 should inherit visibility from parent"829);830831assert!(832is_visible(child2),833"Child2 should inherit visibility from parent"834);835}836837#[test]838fn visibility_propagation_unconditional_visible() {839use Visibility::{Hidden, Inherited, Visible};840841let mut app = App::new();842app.add_systems(Update, visibility_propagate_system);843844let root1 = app.world_mut().spawn(Visible).id();845let root1_child1 = app.world_mut().spawn(Inherited).id();846let root1_child2 = app.world_mut().spawn(Hidden).id();847let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();848let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();849850let root2 = app.world_mut().spawn(Inherited).id();851let root3 = app.world_mut().spawn(Hidden).id();852853app.world_mut()854.entity_mut(root1)855.add_children(&[root1_child1, root1_child2]);856app.world_mut()857.entity_mut(root1_child1)858.add_children(&[root1_child1_grandchild1]);859app.world_mut()860.entity_mut(root1_child2)861.add_children(&[root1_child2_grandchild1]);862863app.update();864865let is_visible = |e: Entity| {866app.world()867.entity(e)868.get::<InheritedVisibility>()869.unwrap()870.get()871};872assert!(873is_visible(root1),874"an unconditionally visible root is visible"875);876assert!(877is_visible(root1_child1),878"an inheriting child of an unconditionally visible parent is visible"879);880assert!(881!is_visible(root1_child2),882"a hidden child on an unconditionally visible parent is hidden"883);884assert!(885is_visible(root1_child1_grandchild1),886"an unconditionally visible child of an inheriting parent is visible"887);888assert!(889is_visible(root1_child2_grandchild1),890"an unconditionally visible child of a hidden parent is visible"891);892assert!(is_visible(root2), "an inheriting root is visible");893assert!(!is_visible(root3), "a hidden root is hidden");894}895896#[test]897fn visibility_propagation_change_detection() {898let mut world = World::new();899let mut schedule = Schedule::default();900schedule.add_systems(visibility_propagate_system);901902// Set up an entity hierarchy.903904let id1 = world.spawn(Visibility::default()).id();905906let id2 = world.spawn(Visibility::default()).id();907world.entity_mut(id1).add_children(&[id2]);908909let id3 = world.spawn(Visibility::Hidden).id();910world.entity_mut(id2).add_children(&[id3]);911912let id4 = world.spawn(Visibility::default()).id();913world.entity_mut(id3).add_children(&[id4]);914915// Test the hierarchy.916917// Make sure the hierarchy is up-to-date.918schedule.run(&mut world);919world.clear_trackers();920921let mut q = world.query::<Ref<InheritedVisibility>>();922923assert!(!q.get(&world, id1).unwrap().is_changed());924assert!(!q.get(&world, id2).unwrap().is_changed());925assert!(!q.get(&world, id3).unwrap().is_changed());926assert!(!q.get(&world, id4).unwrap().is_changed());927928world.clear_trackers();929world.entity_mut(id1).insert(Visibility::Hidden);930schedule.run(&mut world);931932assert!(q.get(&world, id1).unwrap().is_changed());933assert!(q.get(&world, id2).unwrap().is_changed());934assert!(!q.get(&world, id3).unwrap().is_changed());935assert!(!q.get(&world, id4).unwrap().is_changed());936937world.clear_trackers();938schedule.run(&mut world);939940assert!(!q.get(&world, id1).unwrap().is_changed());941assert!(!q.get(&world, id2).unwrap().is_changed());942assert!(!q.get(&world, id3).unwrap().is_changed());943assert!(!q.get(&world, id4).unwrap().is_changed());944945world.clear_trackers();946world.entity_mut(id3).insert(Visibility::Inherited);947schedule.run(&mut world);948949assert!(!q.get(&world, id1).unwrap().is_changed());950assert!(!q.get(&world, id2).unwrap().is_changed());951assert!(!q.get(&world, id3).unwrap().is_changed());952assert!(!q.get(&world, id4).unwrap().is_changed());953954world.clear_trackers();955world.entity_mut(id2).insert(Visibility::Visible);956schedule.run(&mut world);957958assert!(!q.get(&world, id1).unwrap().is_changed());959assert!(q.get(&world, id2).unwrap().is_changed());960assert!(q.get(&world, id3).unwrap().is_changed());961assert!(q.get(&world, id4).unwrap().is_changed());962963world.clear_trackers();964schedule.run(&mut world);965966assert!(!q.get(&world, id1).unwrap().is_changed());967assert!(!q.get(&world, id2).unwrap().is_changed());968assert!(!q.get(&world, id3).unwrap().is_changed());969assert!(!q.get(&world, id4).unwrap().is_changed());970}971972#[test]973fn visibility_propagation_with_invalid_parent() {974let mut world = World::new();975let mut schedule = Schedule::default();976schedule.add_systems(visibility_propagate_system);977978let parent = world.spawn(()).id();979let child = world.spawn(Visibility::default()).id();980world.entity_mut(parent).add_children(&[child]);981982schedule.run(&mut world);983world.clear_trackers();984985let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;986// defaults to same behavior of parent not found: visible = true987assert!(child_visible);988}989990#[test]991fn ensure_visibility_enum_size() {992assert_eq!(1, size_of::<Visibility>());993assert_eq!(1, size_of::<Option<Visibility>>());994}995996#[derive(Component, Default, Clone, Reflect)]997#[require(VisibilityClass)]998#[reflect(Component, Default, Clone)]999#[component(on_add = add_visibility_class::<Self>)]1000struct TestVisibilityClassHook;10011002#[test]1003fn test_add_visibility_class_hook() {1004let mut world = World::new();1005let entity = world.spawn(TestVisibilityClassHook).id();1006let entity_clone = world.spawn_empty().id();1007world1008.entity_mut(entity)1009.clone_with_opt_out(entity_clone, |_| {});10101011let entity_visibility_class = world.entity(entity).get::<VisibilityClass>().unwrap();1012assert_eq!(entity_visibility_class.len(), 1);10131014let entity_clone_visibility_class =1015world.entity(entity_clone).get::<VisibilityClass>().unwrap();1016assert_eq!(entity_clone_visibility_class.len(), 1);1017}1018}101910201021