Path: blob/main/crates/bevy_camera/src/visibility/mod.rs
6599 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// Note: This can't be a `ComponentId` because the visibility classes are copied154// into the render world, and component IDs are per-world.155#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]156#[reflect(Component, Default, Clone)]157pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);158159/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.160///161/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].162/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].163/// Because of this, values of this type will be marked as changed every frame, even when they do not change.164///165/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.166///167/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate168/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility169#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]170#[reflect(Component, Default, Debug, PartialEq, Clone)]171pub struct ViewVisibility(bool);172173impl ViewVisibility {174/// An entity that cannot be seen from any views.175pub const HIDDEN: Self = Self(false);176177/// Returns `true` if the entity is visible in any view.178/// Otherwise, returns `false`.179#[inline]180pub fn get(self) -> bool {181self.0182}183184/// Sets the visibility to `true`. This should not be considered reversible for a given frame,185/// as this component tracks whether or not the entity visible in _any_ view.186///187/// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set188/// to the proper value in [`CheckVisibility`].189///190/// You should only manually set this if you are defining a custom visibility system,191/// in which case the system should be placed in the [`CheckVisibility`] set.192/// For normal user-defined entity visibility, see [`Visibility`].193///194/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate195/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility196#[inline]197pub fn set(&mut self) {198self.0 = true;199}200}201202/// Use this component to opt-out of built-in frustum culling for entities, see203/// [`Frustum`].204///205/// It can be used for example:206/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations,207/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`]208/// to appear in the reflection of a [`Mesh`] within.209#[derive(Debug, Component, Default, Reflect)]210#[reflect(Component, Default, Debug)]211pub struct NoFrustumCulling;212213/// Collection of entities visible from the current view.214///215/// This component contains all entities which are visible from the currently216/// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`]217/// system set. Renderers can use the equivalent `RenderVisibleEntities` to optimize rendering of218/// a particular view, to prevent drawing items not visible from that view.219///220/// This component is intended to be attached to the same entity as the [`Camera`] and221/// the [`Frustum`] defining the view.222#[derive(Clone, Component, Default, Debug, Reflect)]223#[reflect(Component, Default, Debug, Clone)]224pub struct VisibleEntities {225#[reflect(ignore, clone)]226pub entities: TypeIdMap<Vec<Entity>>,227}228229impl VisibleEntities {230pub fn get(&self, type_id: TypeId) -> &[Entity] {231match self.entities.get(&type_id) {232Some(entities) => &entities[..],233None => &[],234}235}236237pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {238self.entities.entry(type_id).or_default()239}240241pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {242self.get(type_id).iter()243}244245pub fn len(&self, type_id: TypeId) -> usize {246self.get(type_id).len()247}248249pub fn is_empty(&self, type_id: TypeId) -> bool {250self.get(type_id).is_empty()251}252253pub fn clear(&mut self, type_id: TypeId) {254self.get_mut(type_id).clear();255}256257pub fn clear_all(&mut self) {258// Don't just nuke the hash table; we want to reuse allocations.259for entities in self.entities.values_mut() {260entities.clear();261}262}263264pub fn push(&mut self, entity: Entity, type_id: TypeId) {265self.get_mut(type_id).push(entity);266}267}268269/// Collection of mesh entities visible for 3D lighting.270///271/// This component contains all mesh entities visible from the current light view.272/// The collection is updated automatically by `bevy_pbr::SimulationLightSystems`.273#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]274#[reflect(Component, Debug, Default, Clone)]275pub struct VisibleMeshEntities {276#[reflect(ignore, clone)]277pub entities: Vec<Entity>,278}279280#[derive(Component, Clone, Debug, Default, Reflect)]281#[reflect(Component, Debug, Default, Clone)]282pub struct CubemapVisibleEntities {283#[reflect(ignore, clone)]284data: [VisibleMeshEntities; 6],285}286287impl CubemapVisibleEntities {288pub fn get(&self, i: usize) -> &VisibleMeshEntities {289&self.data[i]290}291292pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities {293&mut self.data[i]294}295296pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleMeshEntities> {297self.data.iter()298}299300pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleMeshEntities> {301self.data.iter_mut()302}303}304305#[derive(Component, Clone, Debug, Default, Reflect)]306#[reflect(Component, Default, Clone)]307pub struct CascadesVisibleEntities {308/// Map of view entity to the visible entities for each cascade frustum.309#[reflect(ignore, clone)]310pub entities: EntityHashMap<Vec<VisibleMeshEntities>>,311}312313#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]314pub enum VisibilitySystems {315/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,316/// calculating and inserting an [`Aabb`] to relevant entities.317CalculateBounds,318/// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).319UpdateFrusta,320/// Label for the system propagating the [`InheritedVisibility`] in a321/// [`ChildOf`] / [`Children`] hierarchy.322VisibilityPropagate,323/// Label for the [`check_visibility`] system updating [`ViewVisibility`]324/// of each entity and the [`VisibleEntities`] of each view.\325///326/// System order ambiguities between systems in this set are ignored:327/// the order of systems within this set is irrelevant, as [`check_visibility`]328/// assumes that its operations are irreversible during the frame.329CheckVisibility,330/// Label for the `mark_newly_hidden_entities_invisible` system, which sets331/// [`ViewVisibility`] to [`ViewVisibility::HIDDEN`] for entities that no332/// view has marked as visible.333MarkNewlyHiddenEntitiesInvisible,334}335336pub struct VisibilityPlugin;337338impl Plugin for VisibilityPlugin {339fn build(&self, app: &mut bevy_app::App) {340use VisibilitySystems::*;341342app.register_required_components::<Mesh3d, Visibility>()343.register_required_components::<Mesh3d, VisibilityClass>()344.register_required_components::<Mesh2d, Visibility>()345.register_required_components::<Mesh2d, VisibilityClass>()346.configure_sets(347PostUpdate,348(349CalculateBounds350.ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),351UpdateFrusta,352VisibilityPropagate,353)354.before(CheckVisibility)355.after(TransformSystems::Propagate),356)357.configure_sets(358PostUpdate,359MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),360)361.init_resource::<PreviousVisibleEntities>()362.add_systems(363PostUpdate,364(365calculate_bounds.in_set(CalculateBounds),366(visibility_propagate_system, reset_view_visibility)367.in_set(VisibilityPropagate),368check_visibility.in_set(CheckVisibility),369mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),370),371);372app.world_mut()373.register_component_hooks::<Mesh3d>()374.on_add(add_visibility_class::<Mesh3d>);375app.world_mut()376.register_component_hooks::<Mesh2d>()377.on_add(add_visibility_class::<Mesh2d>);378}379}380381/// Computes and adds an [`Aabb`] component to entities with a382/// [`Mesh3d`] component and without a [`NoFrustumCulling`] component.383///384/// This system is used in system set [`VisibilitySystems::CalculateBounds`].385pub fn calculate_bounds(386mut commands: Commands,387meshes: Res<Assets<Mesh>>,388without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,389) {390for (entity, mesh_handle) in &without_aabb {391if let Some(mesh) = meshes.get(mesh_handle)392&& let Some(aabb) = mesh.compute_aabb()393{394commands.entity(entity).try_insert(aabb);395}396}397}398399/// Updates [`Frustum`].400///401/// This system is used in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).402pub fn update_frusta(403mut views: Query<404(&GlobalTransform, &Projection, &mut Frustum),405Or<(Changed<GlobalTransform>, Changed<Projection>)>,406>,407) {408for (transform, projection, mut frustum) in &mut views {409*frustum = projection.compute_frustum(transform);410}411}412413fn visibility_propagate_system(414changed: Query<415(Entity, &Visibility, Option<&ChildOf>, Option<&Children>),416(417With<InheritedVisibility>,418Or<(Changed<Visibility>, Changed<ChildOf>)>,419),420>,421mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,422children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,423) {424for (entity, visibility, child_of, children) in &changed {425let is_visible = match visibility {426Visibility::Visible => true,427Visibility::Hidden => false,428// fall back to true if no parent is found or parent lacks components429Visibility::Inherited => child_of430.and_then(|c| visibility_query.get(c.parent()).ok())431.is_none_or(|(_, x)| x.get()),432};433let (_, mut inherited_visibility) = visibility_query434.get_mut(entity)435.expect("With<InheritedVisibility> ensures this query will return a value");436437// Only update the visibility if it has changed.438// This will also prevent the visibility from propagating multiple times in the same frame439// if this entity's visibility has been updated recursively by its parent.440if inherited_visibility.get() != is_visible {441inherited_visibility.0 = is_visible;442443// Recursively update the visibility of each child.444for &child in children.into_iter().flatten() {445let _ =446propagate_recursive(is_visible, child, &mut visibility_query, &children_query);447}448}449}450}451452fn propagate_recursive(453parent_is_visible: bool,454entity: Entity,455visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,456children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,457// BLOCKED: https://github.com/rust-lang/rust/issues/31436458// We use a result here to use the `?` operator. Ideally we'd use a try block instead459) -> Result<(), ()> {460// Get the visibility components for the current entity.461// If the entity does not have the required components, just return early.462let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;463464let is_visible = match visibility {465Visibility::Visible => true,466Visibility::Hidden => false,467Visibility::Inherited => parent_is_visible,468};469470// Only update the visibility if it has changed.471if inherited_visibility.get() != is_visible {472inherited_visibility.0 = is_visible;473474// Recursively update the visibility of each child.475for &child in children_query.get(entity).ok().into_iter().flatten() {476let _ = propagate_recursive(is_visible, child, visibility_query, children_query);477}478}479480Ok(())481}482483/// Stores all entities that were visible in the previous frame.484///485/// As systems that check visibility judge entities visible, they remove them486/// from this set. Afterward, the `mark_newly_hidden_entities_invisible` system487/// runs and marks every mesh still remaining in this set as hidden.488#[derive(Resource, Default, Deref, DerefMut)]489pub struct PreviousVisibleEntities(EntityHashSet);490491/// Resets the view visibility of every entity.492/// Entities that are visible will be marked as such later this frame493/// by a [`VisibilitySystems::CheckVisibility`] system.494fn reset_view_visibility(495mut query: Query<(Entity, &ViewVisibility)>,496mut previous_visible_entities: ResMut<PreviousVisibleEntities>,497) {498previous_visible_entities.clear();499500query.iter_mut().for_each(|(entity, view_visibility)| {501// Record the entities that were previously visible.502if view_visibility.get() {503previous_visible_entities.insert(entity);504}505});506}507508/// System updating the visibility of entities each frame.509///510/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each511/// frame, it updates the [`ViewVisibility`] of all entities, and for each view512/// also compute the [`VisibleEntities`] for that view.513///514/// To ensure that an entity is checked for visibility, make sure that it has a515/// [`VisibilityClass`] component and that that component is nonempty.516pub fn check_visibility(517mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,518mut view_query: Query<(519Entity,520&mut VisibleEntities,521&Frustum,522Option<&RenderLayers>,523&Camera,524Has<NoCpuCulling>,525)>,526mut visible_aabb_query: Query<(527Entity,528&InheritedVisibility,529&mut ViewVisibility,530&VisibilityClass,531Option<&RenderLayers>,532Option<&Aabb>,533&GlobalTransform,534Has<NoFrustumCulling>,535Has<VisibilityRange>,536)>,537visible_entity_ranges: Option<Res<VisibleEntityRanges>>,538mut previous_visible_entities: ResMut<PreviousVisibleEntities>,539) {540let visible_entity_ranges = visible_entity_ranges.as_deref();541542for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in543&mut view_query544{545if !camera.is_active {546continue;547}548549let view_mask = maybe_view_mask.unwrap_or_default();550551visible_aabb_query.par_iter_mut().for_each_init(552|| thread_queues.borrow_local_mut(),553|queue, query_item| {554let (555entity,556inherited_visibility,557mut view_visibility,558visibility_class,559maybe_entity_mask,560maybe_model_aabb,561transform,562no_frustum_culling,563has_visibility_range,564) = query_item;565566// Skip computing visibility for entities that are configured to be hidden.567// ViewVisibility has already been reset in `reset_view_visibility`.568if !inherited_visibility.get() {569return;570}571572let entity_mask = maybe_entity_mask.unwrap_or_default();573if !view_mask.intersects(entity_mask) {574return;575}576577// If outside of the visibility range, cull.578if has_visibility_range579&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {580!visible_entity_ranges.entity_is_in_range_of_view(entity, view)581})582{583return;584}585586// If we have an aabb, do frustum culling587if !no_frustum_culling588&& !no_cpu_culling589&& let Some(model_aabb) = maybe_model_aabb590{591let world_from_local = transform.affine();592let model_sphere = Sphere {593center: world_from_local.transform_point3a(model_aabb.center),594radius: transform.radius_vec3a(model_aabb.half_extents),595};596// Do quick sphere-based frustum culling597if !frustum.intersects_sphere(&model_sphere, false) {598return;599}600// Do aabb-based frustum culling601if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {602return;603}604}605606// Make sure we don't trigger changed notifications607// unnecessarily by checking whether the flag is set before608// setting it.609if !**view_visibility {610view_visibility.set();611}612613// Add the entity to the queue for all visibility classes the614// entity is in.615for visibility_class_id in visibility_class.iter() {616queue.entry(*visibility_class_id).or_default().push(entity);617}618},619);620621visible_entities.clear_all();622623// Drain all the thread queues into the `visible_entities` list.624for class_queues in thread_queues.iter_mut() {625for (class, entities) in class_queues {626let visible_entities_for_class = visible_entities.get_mut(*class);627for entity in entities.drain(..) {628// As we mark entities as visible, we remove them from the629// `previous_visible_entities` list. At the end, all of the630// entities remaining in `previous_visible_entities` will be631// entities that were visible last frame but are no longer632// visible this frame.633previous_visible_entities.remove(&entity);634635visible_entities_for_class.push(entity);636}637}638}639}640}641642/// Marks any entities that weren't judged visible this frame as invisible.643///644/// As visibility-determining systems run, they remove entities that they judge645/// visible from [`PreviousVisibleEntities`]. At the end of visibility646/// determination, all entities that remain in [`PreviousVisibleEntities`] must647/// be invisible. This system goes through those entities and marks them newly648/// invisible (which sets the change flag for them).649fn mark_newly_hidden_entities_invisible(650mut view_visibilities: Query<&mut ViewVisibility>,651mut previous_visible_entities: ResMut<PreviousVisibleEntities>,652) {653// Whatever previous visible entities are left are entities that were654// visible last frame but just became invisible.655for entity in previous_visible_entities.drain() {656if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) {657*view_visibility = ViewVisibility::HIDDEN;658}659}660}661662/// A generic component add hook that automatically adds the appropriate663/// [`VisibilityClass`] to an entity.664///665/// This can be handy when creating custom renderable components. To use this666/// hook, add it to your renderable component like this:667///668/// ```ignore669/// #[derive(Component)]670/// #[component(on_add = add_visibility_class::<MyComponent>)]671/// struct MyComponent {672/// ...673/// }674/// ```675pub fn add_visibility_class<C>(676mut world: DeferredWorld<'_>,677HookContext { entity, .. }: HookContext,678) where679C: 'static,680{681if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {682visibility_class.push(TypeId::of::<C>());683}684}685686#[cfg(test)]687mod test {688use super::*;689use bevy_app::prelude::*;690691#[test]692fn visibility_propagation() {693let mut app = App::new();694app.add_systems(Update, visibility_propagate_system);695696let root1 = app.world_mut().spawn(Visibility::Hidden).id();697let root1_child1 = app.world_mut().spawn(Visibility::default()).id();698let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();699let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();700let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();701702app.world_mut()703.entity_mut(root1)704.add_children(&[root1_child1, root1_child2]);705app.world_mut()706.entity_mut(root1_child1)707.add_children(&[root1_child1_grandchild1]);708app.world_mut()709.entity_mut(root1_child2)710.add_children(&[root1_child2_grandchild1]);711712let root2 = app.world_mut().spawn(Visibility::default()).id();713let root2_child1 = app.world_mut().spawn(Visibility::default()).id();714let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();715let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();716let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();717718app.world_mut()719.entity_mut(root2)720.add_children(&[root2_child1, root2_child2]);721app.world_mut()722.entity_mut(root2_child1)723.add_children(&[root2_child1_grandchild1]);724app.world_mut()725.entity_mut(root2_child2)726.add_children(&[root2_child2_grandchild1]);727728app.update();729730let is_visible = |e: Entity| {731app.world()732.entity(e)733.get::<InheritedVisibility>()734.unwrap()735.get()736};737assert!(738!is_visible(root1),739"invisibility propagates down tree from root"740);741assert!(742!is_visible(root1_child1),743"invisibility propagates down tree from root"744);745assert!(746!is_visible(root1_child2),747"invisibility propagates down tree from root"748);749assert!(750!is_visible(root1_child1_grandchild1),751"invisibility propagates down tree from root"752);753assert!(754!is_visible(root1_child2_grandchild1),755"invisibility propagates down tree from root"756);757758assert!(759is_visible(root2),760"visibility propagates down tree from root"761);762assert!(763is_visible(root2_child1),764"visibility propagates down tree from root"765);766assert!(767!is_visible(root2_child2),768"visibility propagates down tree from root, but local invisibility is preserved"769);770assert!(771is_visible(root2_child1_grandchild1),772"visibility propagates down tree from root"773);774assert!(775!is_visible(root2_child2_grandchild1),776"child's invisibility propagates down to grandchild"777);778}779780#[test]781fn test_visibility_propagation_on_parent_change() {782// Setup the world and schedule783let mut app = App::new();784785app.add_systems(Update, visibility_propagate_system);786787// Create entities with visibility and hierarchy788let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();789let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();790let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();791let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();792793// Build hierarchy794app.world_mut()795.entity_mut(parent1)796.add_children(&[child1, child2]);797798// Run the system initially to set up visibility799app.update();800801// Change parent visibility to Hidden802app.world_mut()803.entity_mut(parent2)804.insert(Visibility::Visible);805// Simulate a change in the parent component806app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent807808// Run the system again to propagate changes809app.update();810811let is_visible = |e: Entity| {812app.world()813.entity(e)814.get::<InheritedVisibility>()815.unwrap()816.get()817};818819// Retrieve and assert visibility820821assert!(822!is_visible(child1),823"Child1 should inherit visibility from parent"824);825826assert!(827is_visible(child2),828"Child2 should inherit visibility from parent"829);830}831832#[test]833fn visibility_propagation_unconditional_visible() {834use Visibility::{Hidden, Inherited, Visible};835836let mut app = App::new();837app.add_systems(Update, visibility_propagate_system);838839let root1 = app.world_mut().spawn(Visible).id();840let root1_child1 = app.world_mut().spawn(Inherited).id();841let root1_child2 = app.world_mut().spawn(Hidden).id();842let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();843let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();844845let root2 = app.world_mut().spawn(Inherited).id();846let root3 = app.world_mut().spawn(Hidden).id();847848app.world_mut()849.entity_mut(root1)850.add_children(&[root1_child1, root1_child2]);851app.world_mut()852.entity_mut(root1_child1)853.add_children(&[root1_child1_grandchild1]);854app.world_mut()855.entity_mut(root1_child2)856.add_children(&[root1_child2_grandchild1]);857858app.update();859860let is_visible = |e: Entity| {861app.world()862.entity(e)863.get::<InheritedVisibility>()864.unwrap()865.get()866};867assert!(868is_visible(root1),869"an unconditionally visible root is visible"870);871assert!(872is_visible(root1_child1),873"an inheriting child of an unconditionally visible parent is visible"874);875assert!(876!is_visible(root1_child2),877"a hidden child on an unconditionally visible parent is hidden"878);879assert!(880is_visible(root1_child1_grandchild1),881"an unconditionally visible child of an inheriting parent is visible"882);883assert!(884is_visible(root1_child2_grandchild1),885"an unconditionally visible child of a hidden parent is visible"886);887assert!(is_visible(root2), "an inheriting root is visible");888assert!(!is_visible(root3), "a hidden root is hidden");889}890891#[test]892fn visibility_propagation_change_detection() {893let mut world = World::new();894let mut schedule = Schedule::default();895schedule.add_systems(visibility_propagate_system);896897// Set up an entity hierarchy.898899let id1 = world.spawn(Visibility::default()).id();900901let id2 = world.spawn(Visibility::default()).id();902world.entity_mut(id1).add_children(&[id2]);903904let id3 = world.spawn(Visibility::Hidden).id();905world.entity_mut(id2).add_children(&[id3]);906907let id4 = world.spawn(Visibility::default()).id();908world.entity_mut(id3).add_children(&[id4]);909910// Test the hierarchy.911912// Make sure the hierarchy is up-to-date.913schedule.run(&mut world);914world.clear_trackers();915916let mut q = world.query::<Ref<InheritedVisibility>>();917918assert!(!q.get(&world, id1).unwrap().is_changed());919assert!(!q.get(&world, id2).unwrap().is_changed());920assert!(!q.get(&world, id3).unwrap().is_changed());921assert!(!q.get(&world, id4).unwrap().is_changed());922923world.clear_trackers();924world.entity_mut(id1).insert(Visibility::Hidden);925schedule.run(&mut world);926927assert!(q.get(&world, id1).unwrap().is_changed());928assert!(q.get(&world, id2).unwrap().is_changed());929assert!(!q.get(&world, id3).unwrap().is_changed());930assert!(!q.get(&world, id4).unwrap().is_changed());931932world.clear_trackers();933schedule.run(&mut world);934935assert!(!q.get(&world, id1).unwrap().is_changed());936assert!(!q.get(&world, id2).unwrap().is_changed());937assert!(!q.get(&world, id3).unwrap().is_changed());938assert!(!q.get(&world, id4).unwrap().is_changed());939940world.clear_trackers();941world.entity_mut(id3).insert(Visibility::Inherited);942schedule.run(&mut world);943944assert!(!q.get(&world, id1).unwrap().is_changed());945assert!(!q.get(&world, id2).unwrap().is_changed());946assert!(!q.get(&world, id3).unwrap().is_changed());947assert!(!q.get(&world, id4).unwrap().is_changed());948949world.clear_trackers();950world.entity_mut(id2).insert(Visibility::Visible);951schedule.run(&mut world);952953assert!(!q.get(&world, id1).unwrap().is_changed());954assert!(q.get(&world, id2).unwrap().is_changed());955assert!(q.get(&world, id3).unwrap().is_changed());956assert!(q.get(&world, id4).unwrap().is_changed());957958world.clear_trackers();959schedule.run(&mut world);960961assert!(!q.get(&world, id1).unwrap().is_changed());962assert!(!q.get(&world, id2).unwrap().is_changed());963assert!(!q.get(&world, id3).unwrap().is_changed());964assert!(!q.get(&world, id4).unwrap().is_changed());965}966967#[test]968fn visibility_propagation_with_invalid_parent() {969let mut world = World::new();970let mut schedule = Schedule::default();971schedule.add_systems(visibility_propagate_system);972973let parent = world.spawn(()).id();974let child = world.spawn(Visibility::default()).id();975world.entity_mut(parent).add_children(&[child]);976977schedule.run(&mut world);978world.clear_trackers();979980let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;981// defaults to same behavior of parent not found: visible = true982assert!(child_visible);983}984985#[test]986fn ensure_visibility_enum_size() {987assert_eq!(1, size_of::<Visibility>());988assert_eq!(1, size_of::<Option<Visibility>>());989}990}991992993