diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs index e1d54bc1c40ad..206c9e763cf4e 100644 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ b/crates/bevy_render/src/camera/visible_entities.rs @@ -24,22 +24,208 @@ impl VisibleEntities { } } +type LayerMask = u32; + +/// An identifier for a rendering layer. +pub type Layer = u8; + +/// Describes which rendering layers an entity belongs to. +/// +/// Cameras with this component will only render entities with intersecting +/// layers. +/// +/// There are 32 layers numbered `0` - [`TOTAL_LAYERS`]. Entities may belong to one or more +/// layers, or no layer at all. +/// +/// The [`Default`] instance of `RenderLayers` contains layer `0`, the first layer. +/// +/// An entity with this component without any layers is invisible. +/// +/// Entities without this component belong to layer `0`. +#[derive(Copy, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] +#[reflect(Component)] +pub struct RenderLayers(LayerMask); + +impl std::fmt::Debug for RenderLayers { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("RenderLayers") + .field(&self.iter().collect::>()) + .finish() + } +} + +impl std::iter::FromIterator for RenderLayers { + fn from_iter>(i: T) -> Self { + i.into_iter().fold(Self::none(), |mask, g| mask.with(g)) + } +} + +/// Defaults to containing to layer `0`, the first layer. +impl Default for RenderLayers { + fn default() -> Self { + RenderLayers::layer(0) + } +} + +impl RenderLayers { + /// The total number of layers supported. + pub const TOTAL_LAYERS: usize = std::mem::size_of::() * 8; + + /// Create a new `RenderLayers` belonging to the given layer. + pub fn layer(n: Layer) -> Self { + RenderLayers(0).with(n) + } + + /// Create a new `RenderLayers` that belongs to all layers. + pub fn all() -> Self { + RenderLayers(u32::MAX) + } + + /// Create a new `RenderLayers` that belongs to no layers. + pub fn none() -> Self { + RenderLayers(0) + } + + /// Create a `RenderLayers` from a list of layers. + pub fn from_layers(layers: &[Layer]) -> Self { + layers.iter().copied().collect() + } + + /// Add the given layer. + /// + /// This may be called multiple times to allow an entity to belong + /// to multiple rendering layers. The maximum layer is `TOTAL_LAYERS - 1`. + /// + /// # Panics + /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. + pub fn with(mut self, layer: Layer) -> Self { + assert!(usize::from(layer) < Self::TOTAL_LAYERS); + self.0 |= 1 << layer; + self + } + + /// Removes the given rendering layer. + /// + /// # Panics + /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. + pub fn without(mut self, layer: Layer) -> Self { + assert!(usize::from(layer) < Self::TOTAL_LAYERS); + self.0 |= 0 << layer; + self + } + + /// Get an iterator of the layers. + pub fn iter(&self) -> impl Iterator { + let total: Layer = std::convert::TryInto::try_into(Self::TOTAL_LAYERS).unwrap(); + let mask = *self; + (0..total).filter(move |g| RenderLayers::layer(*g).intersects(&mask)) + } + + /// Determine if a `RenderLayers` intersects another. + /// + /// `RenderLayers`s intersect if they share any common layers. + /// + /// A `RenderLayers` with no layers will not match any other + /// `RenderLayers`, even another with no layers. + pub fn intersects(&self, other: &RenderLayers) -> bool { + (self.0 & other.0) > 0 + } +} + +#[cfg(test)] +mod rendering_mask_tests { + use super::{Layer, RenderLayers}; + + #[test] + fn rendering_mask_sanity() { + assert_eq!( + RenderLayers::TOTAL_LAYERS, + 32, + "total layers is what we think it is" + ); + assert_eq!(RenderLayers::layer(0).0, 1, "layer 0 is mask 1"); + assert_eq!(RenderLayers::layer(1).0, 2, "layer 1 is mask 2"); + assert_eq!(RenderLayers::layer(0).with(1).0, 3, "layer 0 + 1 is mask 3"); + assert!( + RenderLayers::layer(1).intersects(&RenderLayers::layer(1)), + "layers match like layers" + ); + assert!( + RenderLayers::layer(0).intersects(&RenderLayers(1)), + "a layer of 0 means the mask is just 1 bit" + ); + + assert!( + RenderLayers::layer(0) + .with(3) + .intersects(&RenderLayers::layer(3)), + "a mask will match another mask containing any similar layers" + ); + + assert!( + RenderLayers::default().intersects(&RenderLayers::default()), + "default masks match each other" + ); + + assert_eq!( + RenderLayers::layer(0).intersects(&RenderLayers::layer(1)), + false, + "masks with differing layers do not match" + ); + assert_eq!( + RenderLayers(0).intersects(&RenderLayers(0)), + false, + "empty masks don't match" + ); + assert_eq!( + RenderLayers::from_layers(&[0, 2, 16, 30]) + .iter() + .collect::>(), + vec![0, 2, 16, 30], + "from_layers and get_layers should roundtrip" + ); + assert_eq!( + format!("{:?}", RenderLayers::from_layers(&[0, 1, 2, 3])).as_str(), + "RenderLayers([0, 1, 2, 3])", + "Debug instance shows layers" + ); + assert_eq!( + RenderLayers::from_layers(&[0, 1, 2]), + >::from_iter(vec![0, 1, 2]), + "from_layers and from_iter are equivalent" + ) + } +} + pub fn visible_entities_system( - mut camera_query: Query<(&Camera, &GlobalTransform, &mut VisibleEntities)>, - visible_query: Query<(Entity, &Visible)>, + mut camera_query: Query<( + &Camera, + &GlobalTransform, + &mut VisibleEntities, + Option<&RenderLayers>, + )>, + visible_query: Query<(Entity, &Visible, Option<&RenderLayers>)>, visible_transform_query: Query<&GlobalTransform, With>, ) { - for (camera, camera_global_transform, mut visible_entities) in camera_query.iter_mut() { + for (camera, camera_global_transform, mut visible_entities, maybe_camera_mask) in + camera_query.iter_mut() + { visible_entities.value.clear(); let camera_position = camera_global_transform.translation; + let camera_mask = maybe_camera_mask.copied().unwrap_or_default(); let mut no_transform_order = 0.0; let mut transparent_entities = Vec::new(); - for (entity, visible) in visible_query.iter() { + for (entity, visible, maybe_entity_mask) in visible_query.iter() { if !visible.is_visible { continue; } + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !camera_mask.intersects(&entity_mask) { + continue; + } + let order = if let Ok(global_transform) = visible_transform_query.get(entity) { let position = global_transform.translation; // smaller distances are sorted to lower indices by using the distance from the camera