Skip to content

Commit

Permalink
Render Layers (#1209)
Browse files Browse the repository at this point in the history
Adds RenderLayers, which enable cameras and entities to opt in to layers that apply to them
  • Loading branch information
schell authored Jan 8, 2021
1 parent 3f2dd22 commit a6a242c
Showing 1 changed file with 190 additions and 4 deletions.
194 changes: 190 additions & 4 deletions crates/bevy_render/src/camera/visible_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>())
.finish()
}
}

impl std::iter::FromIterator<Layer> for RenderLayers {
fn from_iter<T: IntoIterator<Item = Layer>>(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::<LayerMask>() * 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<Item = Layer> {
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<_>>(),
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]),
<RenderLayers as std::iter::FromIterator<Layer>>::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<Visible>>,
) {
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
Expand Down

0 comments on commit a6a242c

Please sign in to comment.