use bevy_asset::Handle;
use bevy_camera::{
visibility::{self, Visibility, VisibilityClass},
Camera, Camera3d,
};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{With, Without},
reflect::ReflectComponent,
resource::Resource,
system::{Commands, Query},
};
use bevy_image::Image;
use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
use bevy_platform::collections::HashSet;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::components::Transform;
use tracing::warn;
pub mod assign;
#[cfg(test)]
mod test;
#[derive(Resource)]
pub struct GlobalClusterSettings {
pub supports_storage_buffers: bool,
pub clustered_decals_are_usable: bool,
pub max_uniform_buffer_clusterable_objects: usize,
pub view_cluster_bindings_max_indices: usize,
}
#[derive(Debug, Copy, Clone, Reflect)]
#[reflect(Clone)]
pub enum ClusterFarZMode {
MaxClusterableObjectRange,
Constant(f32),
}
#[derive(Debug, Copy, Clone, Reflect)]
#[reflect(Default, Clone)]
pub struct ClusterZConfig {
pub first_slice_depth: f32,
pub far_z_mode: ClusterFarZMode,
}
#[derive(Debug, Copy, Clone, Component, Reflect)]
#[reflect(Component, Debug, Default, Clone)]
pub enum ClusterConfig {
None,
Single,
XYZ {
dimensions: UVec3,
z_config: ClusterZConfig,
dynamic_resizing: bool,
},
FixedZ {
total: u32,
z_slices: u32,
z_config: ClusterZConfig,
dynamic_resizing: bool,
},
}
#[derive(Component, Debug, Default)]
pub struct Clusters {
pub tile_size: UVec2,
pub dimensions: UVec3,
pub near: f32,
pub far: f32,
pub clusterable_objects: Vec<VisibleClusterableObjects>,
}
pub struct ClusterVisibilityClass;
#[derive(Clone, Component, Debug, Default)]
pub struct VisibleClusterableObjects {
pub entities: Vec<Entity>,
pub counts: ClusterableObjectCounts,
}
#[derive(Resource, Default)]
pub struct GlobalVisibleClusterableObjects {
pub(crate) entities: HashSet<Entity>,
}
#[derive(Clone, Copy, Default, Debug)]
pub struct ClusterableObjectCounts {
pub point_lights: u32,
pub spot_lights: u32,
pub reflection_probes: u32,
pub irradiance_volumes: u32,
pub decals: u32,
}
#[derive(Component, Debug, Clone, Default, Reflect)]
#[reflect(Component, Debug, Clone, Default)]
#[require(Transform, Visibility, VisibilityClass)]
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
pub struct ClusteredDecal {
pub base_color_texture: Option<Handle<Image>>,
pub normal_map_texture: Option<Handle<Image>>,
pub metallic_roughness_texture: Option<Handle<Image>>,
pub emissive_texture: Option<Handle<Image>>,
pub tag: u32,
}
impl Default for ClusterZConfig {
fn default() -> Self {
Self {
first_slice_depth: 5.0,
far_z_mode: ClusterFarZMode::MaxClusterableObjectRange,
}
}
}
impl Default for ClusterConfig {
fn default() -> Self {
Self::FixedZ {
total: 4096,
z_slices: 24,
z_config: ClusterZConfig::default(),
dynamic_resizing: true,
}
}
}
impl ClusterConfig {
fn dimensions_for_screen_size(&self, screen_size: UVec2) -> UVec3 {
match &self {
ClusterConfig::None => UVec3::ZERO,
ClusterConfig::Single => UVec3::ONE,
ClusterConfig::XYZ { dimensions, .. } => *dimensions,
ClusterConfig::FixedZ {
total, z_slices, ..
} => {
let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y)
.expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values")
.ratio();
let mut z_slices = *z_slices;
if *total < z_slices {
warn!("ClusterConfig has more z-slices than total clusters!");
z_slices = *total;
}
let per_layer = *total as f32 / z_slices as f32;
let y = f32::sqrt(per_layer / aspect_ratio);
let mut x = (y * aspect_ratio) as u32;
let mut y = y as u32;
if x == 0 {
x = 1;
y = per_layer as u32;
}
if y == 0 {
x = per_layer as u32;
y = 1;
}
UVec3::new(x, y, z_slices)
}
}
}
fn first_slice_depth(&self) -> f32 {
match self {
ClusterConfig::None | ClusterConfig::Single => 0.0,
ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
z_config.first_slice_depth
}
}
}
fn far_z_mode(&self) -> ClusterFarZMode {
match self {
ClusterConfig::None => ClusterFarZMode::Constant(0.0),
ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange,
ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
z_config.far_z_mode
}
}
}
fn dynamic_resizing(&self) -> bool {
match self {
ClusterConfig::None | ClusterConfig::Single => false,
ClusterConfig::XYZ {
dynamic_resizing, ..
}
| ClusterConfig::FixedZ {
dynamic_resizing, ..
} => *dynamic_resizing,
}
}
}
impl Clusters {
fn update(&mut self, screen_size: UVec2, requested_dimensions: UVec3) {
debug_assert!(
requested_dimensions.x > 0 && requested_dimensions.y > 0 && requested_dimensions.z > 0
);
let tile_size = (screen_size.as_vec2() / requested_dimensions.xy().as_vec2())
.ceil()
.as_uvec2()
.max(UVec2::ONE);
self.tile_size = tile_size;
self.dimensions = (screen_size.as_vec2() / tile_size.as_vec2())
.ceil()
.as_uvec2()
.extend(requested_dimensions.z)
.max(UVec3::ONE);
debug_assert!(self.dimensions.x * self.dimensions.y * self.dimensions.z <= 4096);
}
fn clear(&mut self) {
self.tile_size = UVec2::ONE;
self.dimensions = UVec3::ZERO;
self.near = 0.0;
self.far = 0.0;
self.clusterable_objects.clear();
}
}
pub fn add_clusters(
mut commands: Commands,
cameras: Query<(Entity, Option<&ClusterConfig>, &Camera), (Without<Clusters>, With<Camera3d>)>,
) {
for (entity, config, camera) in &cameras {
if !camera.is_active {
continue;
}
let config = config.copied().unwrap_or_default();
commands
.entity(entity)
.insert((Clusters::default(), config));
}
}
impl VisibleClusterableObjects {
#[inline]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
self.entities.iter()
}
#[inline]
pub fn len(&self) -> usize {
self.entities.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
}
impl GlobalVisibleClusterableObjects {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Entity> {
self.entities.iter()
}
#[inline]
pub fn contains(&self, entity: Entity) -> bool {
self.entities.contains(&entity)
}
}