mod render_layers;
pub use render_layers::*;
use bevy_app::{CoreStage, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
use bevy_reflect::Reflect;
use bevy_reflect::{std_traits::ReflectDefault, FromReflect};
use bevy_transform::components::GlobalTransform;
use bevy_transform::TransformSystem;
use std::cell::Cell;
use thread_local::ThreadLocal;
use crate::{
camera::{
camera_system, Camera, CameraProjection, OrthographicProjection, PerspectiveProjection,
Projection,
},
mesh::Mesh,
primitives::{Aabb, Frustum, Sphere},
};
#[derive(Component, Clone, Reflect, FromReflect, Debug)]
#[reflect(Component, Default)]
pub struct Visibility {
pub is_visible: bool,
}
impl Default for Visibility {
fn default() -> Self {
Visibility::VISIBLE
}
}
impl Visibility {
pub const VISIBLE: Self = Visibility { is_visible: true };
pub const INVISIBLE: Self = Visibility { is_visible: false };
pub fn toggle(&mut self) {
self.is_visible = !self.is_visible;
}
}
#[derive(Component, Clone, Reflect, Debug, Eq, PartialEq)]
#[reflect(Component, Default)]
pub struct ComputedVisibility {
is_visible_in_hierarchy: bool,
is_visible_in_view: bool,
}
impl Default for ComputedVisibility {
fn default() -> Self {
Self::INVISIBLE
}
}
impl ComputedVisibility {
pub const INVISIBLE: Self = ComputedVisibility {
is_visible_in_hierarchy: false,
is_visible_in_view: false,
};
#[inline]
pub fn is_visible(&self) -> bool {
self.is_visible_in_hierarchy && self.is_visible_in_view
}
#[inline]
pub fn is_visible_in_hierarchy(&self) -> bool {
self.is_visible_in_hierarchy
}
#[inline]
pub fn is_visible_in_view(&self) -> bool {
self.is_visible_in_view
}
#[inline]
pub fn set_visible_in_view(&mut self) {
self.is_visible_in_view = true;
}
}
#[derive(Bundle, Debug, Default)]
pub struct VisibilityBundle {
pub visibility: Visibility,
pub computed: ComputedVisibility,
}
#[derive(Component)]
pub struct NoFrustumCulling;
#[derive(Clone, Component, Default, Debug, Reflect)]
#[reflect(Component)]
pub struct VisibleEntities {
#[reflect(ignore)]
pub entities: Vec<Entity>,
}
impl VisibleEntities {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
self.entities.iter()
}
pub fn len(&self) -> usize {
self.entities.len()
}
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum VisibilitySystems {
CalculateBounds,
UpdateOrthographicFrusta,
UpdatePerspectiveFrusta,
UpdateProjectionFrusta,
VisibilityPropagate,
CheckVisibility,
}
pub struct VisibilityPlugin;
impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
use VisibilitySystems::*;
app.add_system_to_stage(
CoreStage::PostUpdate,
calculate_bounds.label(CalculateBounds),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_frusta::<OrthographicProjection>
.label(UpdateOrthographicFrusta)
.after(camera_system::<OrthographicProjection>)
.after(TransformSystem::TransformPropagate)
.ambiguous_with(update_frusta::<PerspectiveProjection>)
.ambiguous_with(update_frusta::<Projection>),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_frusta::<PerspectiveProjection>
.label(UpdatePerspectiveFrusta)
.after(camera_system::<PerspectiveProjection>)
.after(TransformSystem::TransformPropagate)
.ambiguous_with(update_frusta::<Projection>),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_frusta::<Projection>
.label(UpdateProjectionFrusta)
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
visibility_propagate_system.label(VisibilityPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
check_visibility
.label(CheckVisibility)
.after(CalculateBounds)
.after(UpdateOrthographicFrusta)
.after(UpdatePerspectiveFrusta)
.after(UpdateProjectionFrusta)
.after(VisibilityPropagate)
.after(TransformSystem::TransformPropagate),
);
}
}
pub fn calculate_bounds(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
without_aabb: Query<(Entity, &Handle<Mesh>), (Without<Aabb>, Without<NoFrustumCulling>)>,
) {
for (entity, mesh_handle) in &without_aabb {
if let Some(mesh) = meshes.get(mesh_handle) {
if let Some(aabb) = mesh.compute_aabb() {
commands.entity(entity).insert(aabb);
}
}
}
}
pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
mut views: Query<(&GlobalTransform, &T, &mut Frustum)>,
) {
for (transform, projection, mut frustum) in &mut views {
let view_projection =
projection.get_projection_matrix() * transform.compute_matrix().inverse();
*frustum = Frustum::from_view_projection(
&view_projection,
&transform.translation(),
&transform.back(),
projection.far(),
);
}
}
fn visibility_propagate_system(
mut root_query: Query<
(
Option<&Children>,
&Visibility,
&mut ComputedVisibility,
Entity,
),
Without<Parent>,
>,
mut visibility_query: Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
) {
for (children, visibility, mut computed_visibility, entity) in root_query.iter_mut() {
computed_visibility.is_visible_in_hierarchy = visibility.is_visible;
computed_visibility.is_visible_in_view = false;
if let Some(children) = children {
for child in children.iter() {
let _ = propagate_recursive(
computed_visibility.is_visible_in_hierarchy,
&mut visibility_query,
&children_query,
*child,
entity,
);
}
}
}
}
fn propagate_recursive(
parent_visible: bool,
visibility_query: &mut Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: &Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
entity: Entity,
expected_parent: Entity,
) -> Result<(), ()> {
let is_visible = {
let (visibility, mut computed_visibility, child_parent) =
visibility_query.get_mut(entity).map_err(drop)?;
assert_eq!(
child_parent.get(), expected_parent,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
computed_visibility.is_visible_in_hierarchy = visibility.is_visible && parent_visible;
computed_visibility.is_visible_in_view = false;
computed_visibility.is_visible_in_hierarchy
};
for child in children_query.get(entity).map_err(drop)?.iter() {
let _ = propagate_recursive(is_visible, visibility_query, children_query, *child, entity);
}
Ok(())
}
const VISIBLE_ENTITIES_QUERY_BATCH_SIZE: usize = 1024;
pub fn check_visibility(
mut thread_queues: Local<ThreadLocal<Cell<Vec<Entity>>>>,
mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>,
mut visible_aabb_query: Query<(
Entity,
&mut ComputedVisibility,
Option<&RenderLayers>,
&Aabb,
&GlobalTransform,
Option<&NoFrustumCulling>,
)>,
mut visible_no_aabb_query: Query<
(Entity, &mut ComputedVisibility, Option<&RenderLayers>),
Without<Aabb>,
>,
) {
for (mut visible_entities, frustum, maybe_view_mask) in &mut view_query {
let view_mask = maybe_view_mask.copied().unwrap_or_default();
visible_entities.entities.clear();
visible_aabb_query.par_for_each_mut(
VISIBLE_ENTITIES_QUERY_BATCH_SIZE,
|(
entity,
mut computed_visibility,
maybe_entity_mask,
model_aabb,
transform,
maybe_no_frustum_culling,
)| {
if !computed_visibility.is_visible_in_hierarchy() {
return;
}
let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
return;
}
if maybe_no_frustum_culling.is_none() {
let model = transform.compute_matrix();
let model_sphere = Sphere {
center: model.transform_point3a(model_aabb.center),
radius: transform.radius_vec3a(model_aabb.half_extents),
};
if !frustum.intersects_sphere(&model_sphere, false) {
return;
}
if !frustum.intersects_obb(model_aabb, &model, false) {
return;
}
}
computed_visibility.is_visible_in_view = true;
let cell = thread_queues.get_or_default();
let mut queue = cell.take();
queue.push(entity);
cell.set(queue);
},
);
visible_no_aabb_query.par_for_each_mut(
VISIBLE_ENTITIES_QUERY_BATCH_SIZE,
|(entity, mut computed_visibility, maybe_entity_mask)| {
if !computed_visibility.is_visible_in_hierarchy() {
return;
}
let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
return;
}
computed_visibility.is_visible_in_view = true;
let cell = thread_queues.get_or_default();
let mut queue = cell.take();
queue.push(entity);
cell.set(queue);
},
);
for cell in thread_queues.iter_mut() {
visible_entities.entities.append(cell.get_mut());
}
}
}
#[cfg(test)]
mod test {
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use super::*;
use bevy_hierarchy::BuildWorldChildren;
#[test]
fn visibility_propagation() {
let mut app = App::new();
app.add_system(visibility_propagate_system);
let root1 = app
.world
.spawn((
Visibility { is_visible: false },
ComputedVisibility::default(),
))
.id();
let root1_child1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root1_child2 = app
.world
.spawn((
Visibility { is_visible: false },
ComputedVisibility::default(),
))
.id();
let root1_child1_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root1_child2_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
app.world
.entity_mut(root1)
.push_children(&[root1_child1, root1_child2]);
app.world
.entity_mut(root1_child1)
.push_children(&[root1_child1_grandchild1]);
app.world
.entity_mut(root1_child2)
.push_children(&[root1_child2_grandchild1]);
let root2 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2_child1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2_child2 = app
.world
.spawn((
Visibility { is_visible: false },
ComputedVisibility::default(),
))
.id();
let root2_child1_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2_child2_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
app.world
.entity_mut(root2)
.push_children(&[root2_child1, root2_child2]);
app.world
.entity_mut(root2_child1)
.push_children(&[root2_child1_grandchild1]);
app.world
.entity_mut(root2_child2)
.push_children(&[root2_child2_grandchild1]);
app.update();
let is_visible = |e: Entity| {
app.world
.entity(e)
.get::<ComputedVisibility>()
.unwrap()
.is_visible_in_hierarchy
};
assert!(
!is_visible(root1),
"invisibility propagates down tree from root"
);
assert!(
!is_visible(root1_child1),
"invisibility propagates down tree from root"
);
assert!(
!is_visible(root1_child2),
"invisibility propagates down tree from root"
);
assert!(
!is_visible(root1_child1_grandchild1),
"invisibility propagates down tree from root"
);
assert!(
!is_visible(root1_child2_grandchild1),
"invisibility propagates down tree from root"
);
assert!(
is_visible(root2),
"visibility propagates down tree from root"
);
assert!(
is_visible(root2_child1),
"visibility propagates down tree from root"
);
assert!(
!is_visible(root2_child2),
"visibility propagates down tree from root, but local invisibility is preserved"
);
assert!(
is_visible(root2_child1_grandchild1),
"visibility propagates down tree from root"
);
assert!(
!is_visible(root2_child2_grandchild1),
"child's invisibility propagates down to grandchild"
);
}
}