Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rendering masks #1209

Merged
merged 13 commits into from
Jan 8, 2021
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.
Copy link
Member

@DJMcNab DJMcNab Jan 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still cautious about making this guarantee, since we still seem to require that every entity has a Visible, which this overlaps with.

That being said, I don't think there is necessarily better behaviour. Maybe we could have a warning log which prints once (i.e. if any entity matches this, then print and set an AtomicBool to true)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a bit sketchy but it does help describe the feature. I think this is the best we can do at the moment. I personally plan on using the "invisiblity" of RenderLayers::none() so I don't think we should add a warning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I thought a bit about just replacing Visible with RenderLayers. But I think we need a "global" visible/invisible toggle:

  • That way visibility can still be toggled without needing to know what layers the current entity is visible on
  • Visibility will likely eventually propagate down a hierarchy, while RenderLayers might not. Its worth discussing at some point, but imo doesn't need to be settled in this pr.
  • I want entity visibility to be easily toggle-able in the editor from the "Entity Inspector" (ex: have an "eye" toggle next to the entity name), which would be much harder for arbitrary layers

///
/// 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