1use bevy_asset::Handle;
4use bevy_camera::{
5 visibility::{self, Visibility, VisibilityClass},
6 Camera, Camera3d,
7};
8use bevy_ecs::{
9 component::Component,
10 entity::Entity,
11 query::{With, Without},
12 reflect::ReflectComponent,
13 resource::Resource,
14 system::{Commands, Query},
15};
16use bevy_image::Image;
17use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
18use bevy_platform::collections::HashSet;
19use bevy_reflect::{std_traits::ReflectDefault, Reflect};
20use bevy_transform::components::Transform;
21use tracing::warn;
22
23pub mod assign;
24
25#[cfg(test)]
26mod test;
27
28#[derive(Resource)]
38pub struct GlobalClusterSettings {
39 pub supports_storage_buffers: bool,
40 pub clustered_decals_are_usable: bool,
41 pub max_uniform_buffer_clusterable_objects: usize,
42 pub view_cluster_bindings_max_indices: usize,
43}
44
45#[derive(Debug, Copy, Clone, Reflect)]
48#[reflect(Clone)]
49pub enum ClusterFarZMode {
50 MaxClusterableObjectRange,
55 Constant(f32),
57}
58
59#[derive(Debug, Copy, Clone, Reflect)]
61#[reflect(Default, Clone)]
62pub struct ClusterZConfig {
63 pub first_slice_depth: f32,
65 pub far_z_mode: ClusterFarZMode,
67}
68
69#[derive(Debug, Copy, Clone, Component, Reflect)]
71#[reflect(Component, Debug, Default, Clone)]
72pub enum ClusterConfig {
73 None,
75 Single,
78 XYZ {
80 dimensions: UVec3,
81 z_config: ClusterZConfig,
82 dynamic_resizing: bool,
85 },
86 FixedZ {
92 total: u32,
93 z_slices: u32,
94 z_config: ClusterZConfig,
95 dynamic_resizing: bool,
98 },
99}
100
101#[derive(Component, Debug, Default)]
102pub struct Clusters {
103 pub tile_size: UVec2,
105 pub dimensions: UVec3,
107 pub near: f32,
110 pub far: f32,
111 pub clusterable_objects: Vec<VisibleClusterableObjects>,
112}
113
114pub struct ClusterVisibilityClass;
118
119#[derive(Clone, Component, Debug, Default)]
120pub struct VisibleClusterableObjects {
121 pub entities: Vec<Entity>,
122 pub counts: ClusterableObjectCounts,
123}
124
125#[derive(Resource, Default)]
126pub struct GlobalVisibleClusterableObjects {
127 pub(crate) entities: HashSet<Entity>,
128}
129
130#[derive(Clone, Copy, Default, Debug)]
135pub struct ClusterableObjectCounts {
136 pub point_lights: u32,
138 pub spot_lights: u32,
140 pub reflection_probes: u32,
142 pub irradiance_volumes: u32,
144 pub decals: u32,
146}
147
148#[derive(Component, Debug, Clone, Reflect)]
159#[reflect(Component, Debug, Clone)]
160#[require(Transform, Visibility, VisibilityClass)]
161#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
162pub struct ClusteredDecal {
163 pub image: Handle<Image>,
169
170 pub tag: u32,
174}
175
176impl Default for ClusterZConfig {
177 fn default() -> Self {
178 Self {
179 first_slice_depth: 5.0,
180 far_z_mode: ClusterFarZMode::MaxClusterableObjectRange,
181 }
182 }
183}
184
185impl Default for ClusterConfig {
186 fn default() -> Self {
187 Self::FixedZ {
190 total: 4096,
191 z_slices: 24,
192 z_config: ClusterZConfig::default(),
193 dynamic_resizing: true,
194 }
195 }
196}
197
198impl ClusterConfig {
199 fn dimensions_for_screen_size(&self, screen_size: UVec2) -> UVec3 {
200 match &self {
201 ClusterConfig::None => UVec3::ZERO,
202 ClusterConfig::Single => UVec3::ONE,
203 ClusterConfig::XYZ { dimensions, .. } => *dimensions,
204 ClusterConfig::FixedZ {
205 total, z_slices, ..
206 } => {
207 let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y)
208 .expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values")
209 .ratio();
210 let mut z_slices = *z_slices;
211 if *total < z_slices {
212 warn!("ClusterConfig has more z-slices than total clusters!");
213 z_slices = *total;
214 }
215 let per_layer = *total as f32 / z_slices as f32;
216
217 let y = f32::sqrt(per_layer / aspect_ratio);
218
219 let mut x = (y * aspect_ratio) as u32;
220 let mut y = y as u32;
221
222 if x == 0 {
224 x = 1;
225 y = per_layer as u32;
226 }
227 if y == 0 {
228 x = per_layer as u32;
229 y = 1;
230 }
231
232 UVec3::new(x, y, z_slices)
233 }
234 }
235 }
236
237 fn first_slice_depth(&self) -> f32 {
238 match self {
239 ClusterConfig::None | ClusterConfig::Single => 0.0,
240 ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
241 z_config.first_slice_depth
242 }
243 }
244 }
245
246 fn far_z_mode(&self) -> ClusterFarZMode {
247 match self {
248 ClusterConfig::None => ClusterFarZMode::Constant(0.0),
249 ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange,
250 ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
251 z_config.far_z_mode
252 }
253 }
254 }
255
256 fn dynamic_resizing(&self) -> bool {
257 match self {
258 ClusterConfig::None | ClusterConfig::Single => false,
259 ClusterConfig::XYZ {
260 dynamic_resizing, ..
261 }
262 | ClusterConfig::FixedZ {
263 dynamic_resizing, ..
264 } => *dynamic_resizing,
265 }
266 }
267}
268
269impl Clusters {
270 fn update(&mut self, screen_size: UVec2, requested_dimensions: UVec3) {
271 debug_assert!(
272 requested_dimensions.x > 0 && requested_dimensions.y > 0 && requested_dimensions.z > 0
273 );
274
275 let tile_size = (screen_size.as_vec2() / requested_dimensions.xy().as_vec2())
276 .ceil()
277 .as_uvec2()
278 .max(UVec2::ONE);
279 self.tile_size = tile_size;
280 self.dimensions = (screen_size.as_vec2() / tile_size.as_vec2())
281 .ceil()
282 .as_uvec2()
283 .extend(requested_dimensions.z)
284 .max(UVec3::ONE);
285
286 debug_assert!(self.dimensions.x * self.dimensions.y * self.dimensions.z <= 4096);
288 }
289 fn clear(&mut self) {
290 self.tile_size = UVec2::ONE;
291 self.dimensions = UVec3::ZERO;
292 self.near = 0.0;
293 self.far = 0.0;
294 self.clusterable_objects.clear();
295 }
296}
297
298pub fn add_clusters(
299 mut commands: Commands,
300 cameras: Query<(Entity, Option<&ClusterConfig>, &Camera), (Without<Clusters>, With<Camera3d>)>,
301) {
302 for (entity, config, camera) in &cameras {
303 if !camera.is_active {
304 continue;
305 }
306
307 let config = config.copied().unwrap_or_default();
308 commands
311 .entity(entity)
312 .insert((Clusters::default(), config));
313 }
314}
315
316impl VisibleClusterableObjects {
317 #[inline]
318 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
319 self.entities.iter()
320 }
321
322 #[inline]
323 pub fn len(&self) -> usize {
324 self.entities.len()
325 }
326
327 #[inline]
328 pub fn is_empty(&self) -> bool {
329 self.entities.is_empty()
330 }
331}
332
333impl GlobalVisibleClusterableObjects {
334 #[inline]
335 pub fn iter(&self) -> impl Iterator<Item = &Entity> {
336 self.entities.iter()
337 }
338
339 #[inline]
340 pub fn contains(&self, entity: Entity) -> bool {
341 self.entities.contains(&entity)
342 }
343}