1use bevy_camera::{
4 primitives::{Aabb, Frustum, Sphere},
5 visibility::{RenderLayers, ViewVisibility},
6 Camera,
7};
8use bevy_ecs::{
9 entity::Entity,
10 query::{Has, With},
11 system::{Local, Query, Res},
12};
13use bevy_math::{
14 ops::{self, sin_cos},
15 primitives::HalfSpace,
16 Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _,
17};
18use bevy_transform::components::GlobalTransform;
19use tracing::{error, warn};
20
21use super::{ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings};
22use crate::{
23 cluster::ClusterableObjects, EnvironmentMapLight, LightProbe, PointLight, SpotLight,
24 VolumetricLight,
25};
26
27const NDC_MIN: Vec2 = Vec2::NEG_ONE;
28const NDC_MAX: Vec2 = Vec2::ONE;
29
30const VEC2_HALF: Vec2 = Vec2::splat(0.5);
31const VEC2_HALF_NEGATIVE_Y: Vec2 = Vec2::new(0.5, -0.5);
32
33const DEFAULT_FAR_DEPTH: f32 = 1000.0;
39
40#[derive(Clone, Debug)]
42pub(crate) struct ClusterableObjectAssignmentData {
43 entity: Entity,
44 transform: GlobalTransform,
47 range: f32,
48 object_type: ClusterableObjectType,
49 render_layers: RenderLayers,
50}
51
52impl ClusterableObjectAssignmentData {
53 pub fn sphere(&self) -> Sphere {
54 Sphere {
55 center: self.transform.translation_vec3a(),
56 radius: self.range,
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
64pub enum ClusterableObjectType {
65 PointLight {
67 shadow_maps_enabled: bool,
71
72 volumetric: bool,
76 },
77
78 SpotLight {
80 shadow_maps_enabled: bool,
84
85 volumetric: bool,
89
90 outer_angle: f32,
92 },
93
94 ReflectionProbe,
96
97 IrradianceVolume,
99
100 Decal,
102}
103
104impl ClusterableObjectType {
105 pub fn ordering(&self) -> (u8, bool, bool) {
113 match *self {
114 ClusterableObjectType::PointLight {
115 shadow_maps_enabled,
116 volumetric,
117 } => (0, !shadow_maps_enabled, !volumetric),
118 ClusterableObjectType::SpotLight {
119 shadow_maps_enabled,
120 volumetric,
121 ..
122 } => (1, !shadow_maps_enabled, !volumetric),
123 ClusterableObjectType::ReflectionProbe => (2, false, false),
124 ClusterableObjectType::IrradianceVolume => (3, false, false),
125 ClusterableObjectType::Decal => (4, false, false),
126 }
127 }
128}
129
130pub(crate) fn assign_objects_to_clusters(
134 mut views: Query<(
135 &GlobalTransform,
136 &Camera,
137 &Frustum,
138 Option<&ClusterConfig>,
139 &mut Clusters,
140 Option<&RenderLayers>,
141 )>,
142 point_lights_query: Query<(
143 Entity,
144 &GlobalTransform,
145 &ViewVisibility,
146 &PointLight,
147 Option<&RenderLayers>,
148 Option<&VolumetricLight>,
149 )>,
150 spot_lights_query: Query<(
151 Entity,
152 &GlobalTransform,
153 &ViewVisibility,
154 &SpotLight,
155 Option<&RenderLayers>,
156 Option<&VolumetricLight>,
157 )>,
158 light_probes_query: Query<
159 (
160 Entity,
161 &GlobalTransform,
162 &ViewVisibility,
163 Has<EnvironmentMapLight>,
164 ),
165 With<LightProbe>,
166 >,
167 decals_query: Query<(Entity, &GlobalTransform, &ViewVisibility), With<ClusteredDecal>>,
168 mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
169 mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
170 mut max_clusterable_objects_warning_emitted: Local<bool>,
171 global_cluster_settings: Option<Res<GlobalClusterSettings>>,
172) {
173 let Some(global_cluster_settings) = global_cluster_settings else {
174 return;
175 };
176
177 clusterable_objects.clear();
178
179 if global_cluster_settings.gpu_clustering.is_none() {
181 clusterable_objects.extend(point_lights_query.iter().filter_map(
183 |(entity, transform, view_visibility, point_light, maybe_layers, volumetric)| {
184 if view_visibility.get() {
185 Some(ClusterableObjectAssignmentData {
186 entity,
187 transform: GlobalTransform::from_translation(transform.translation()),
188 range: point_light.range,
189 object_type: ClusterableObjectType::PointLight {
190 shadow_maps_enabled: point_light.shadow_maps_enabled,
191 volumetric: volumetric.is_some(),
192 },
193 render_layers: maybe_layers.unwrap_or_default().clone(),
194 })
195 } else {
196 None
197 }
198 },
199 ));
200 clusterable_objects.extend(spot_lights_query.iter().filter_map(
201 |(entity, transform, view_visibility, spot_light, maybe_layers, volumetric)| {
202 if view_visibility.get() {
203 Some(ClusterableObjectAssignmentData {
204 entity,
205 transform: *transform,
206 range: spot_light.range,
207 object_type: ClusterableObjectType::SpotLight {
208 outer_angle: spot_light.outer_angle,
209 shadow_maps_enabled: spot_light.shadow_maps_enabled,
210 volumetric: volumetric.is_some(),
211 },
212 render_layers: maybe_layers.unwrap_or_default().clone(),
213 })
214 } else {
215 None
216 }
217 },
218 ));
219
220 if global_cluster_settings.supports_storage_buffers {
227 clusterable_objects.extend(light_probes_query.iter().filter_map(
228 |(entity, transform, view_visibility, is_reflection_probe)| {
229 if view_visibility.get() {
230 Some(ClusterableObjectAssignmentData {
231 entity,
232 transform: *transform,
233 range: transform.radius_vec3a(Vec3A::ONE),
234 object_type: if is_reflection_probe {
235 ClusterableObjectType::ReflectionProbe
236 } else {
237 ClusterableObjectType::IrradianceVolume
238 },
239 render_layers: RenderLayers::default(),
240 })
241 } else {
242 None
243 }
244 },
245 ));
246 }
247
248 if global_cluster_settings.clustered_decals_are_usable {
250 clusterable_objects.extend(decals_query.iter().filter_map(
251 |(entity, transform, view_visibility)| {
252 if view_visibility.get() {
253 Some(ClusterableObjectAssignmentData {
254 entity,
255 transform: *transform,
256 range: transform.scale().length(),
257 object_type: ClusterableObjectType::Decal,
258 render_layers: RenderLayers::default(),
259 })
260 } else {
261 None
262 }
263 },
264 ));
265 }
266
267 if clusterable_objects.len()
268 > global_cluster_settings.max_uniform_buffer_clusterable_objects
269 && !global_cluster_settings.supports_storage_buffers
270 {
271 clusterable_objects.sort_by_cached_key(|clusterable_object| {
272 (
273 clusterable_object.object_type.ordering(),
274 clusterable_object.entity,
275 )
276 });
277
278 if clusterable_objects.len()
279 > global_cluster_settings.max_uniform_buffer_clusterable_objects
280 && !*max_clusterable_objects_warning_emitted
281 {
282 warn!(
283 "max_uniform_buffer_clusterable_objects ({}) exceeded",
284 global_cluster_settings.max_uniform_buffer_clusterable_objects
285 );
286 *max_clusterable_objects_warning_emitted = true;
287 }
288
289 clusterable_objects
290 .truncate(global_cluster_settings.max_uniform_buffer_clusterable_objects);
291 }
292 }
293
294 for (camera_transform, camera, frustum, config, clusters, maybe_layers) in &mut views {
295 let view_layers = maybe_layers.unwrap_or_default();
296 let clusters = clusters.into_inner();
297 let config = config.copied().unwrap_or_default();
298
299 if matches!(config, ClusterConfig::None) {
300 clusters.clear(&global_cluster_settings);
301 continue;
302 }
303
304 let screen_size = match camera.physical_viewport_size() {
305 Some(screen_size) if screen_size.x != 0 && screen_size.y != 0 => screen_size,
306 _ => {
307 clusters.clear(&global_cluster_settings);
308 continue;
309 }
310 };
311
312 let mut requested_cluster_dimensions = config.dimensions_for_screen_size(screen_size);
313
314 let world_from_view = camera_transform.affine();
315 let view_from_world_scale = camera_transform.compute_transform().scale.recip();
316 let view_from_world_scale_max = view_from_world_scale.abs().max_element();
317 let view_from_world = Mat4::from(world_from_view.inverse());
318 let is_orthographic = camera.clip_from_view().w_axis.w == 1.0;
319
320 let far_z = match config.far_z_mode() {
321 ClusterFarZMode::MaxClusterableObjectRange => {
322 clusters.last_frame_farthest_z.unwrap_or(DEFAULT_FAR_DEPTH)
323 }
324 ClusterFarZMode::Constant(far) => far,
325 };
326 let first_slice_depth = match (is_orthographic, requested_cluster_dimensions.z) {
327 (true, _) => {
328 (camera.clip_from_view().w_axis.z - 1.0) / camera.clip_from_view().z_axis.z
337 }
338 (false, 1) => config.first_slice_depth().max(far_z),
339 _ => config.first_slice_depth(),
340 };
341 let first_slice_depth = first_slice_depth * view_from_world_scale.z;
342
343 let far_z = far_z.max(first_slice_depth);
345 let cluster_factors = calculate_cluster_factors(
346 first_slice_depth,
347 far_z,
348 requested_cluster_dimensions.z as f32,
349 is_orthographic,
350 );
351
352 if config.dynamic_resizing()
355 && let Some(last_frame_cluster_index_count) =
356 clusters.last_frame_total_cluster_index_count
357 && last_frame_cluster_index_count
358 > global_cluster_settings.view_cluster_bindings_max_indices
359 {
360 let index_ratio = global_cluster_settings.view_cluster_bindings_max_indices as f32
367 / last_frame_cluster_index_count as f32;
368 let xy_ratio = index_ratio.sqrt();
369
370 requested_cluster_dimensions.x =
371 ((requested_cluster_dimensions.x as f32 * xy_ratio).floor() as u32).max(1);
372 requested_cluster_dimensions.y =
373 ((requested_cluster_dimensions.y as f32 * xy_ratio).floor() as u32).max(1);
374 }
375
376 clusters.update(screen_size, requested_cluster_dimensions);
377 clusters.near = first_slice_depth;
378 clusters.far = far_z;
379
380 debug_assert!(
382 clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z <= 4096
383 );
384
385 let view_from_clip = camera.clip_from_view().inverse();
386
387 let cluster_count =
388 (clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize;
389 clusters.reset_for_new_frame(cluster_count, &global_cluster_settings);
390
391 let (mut total_cluster_index_count, mut farthest_z) = (0, 0.0f32);
392 let view_from_world_row_2 = view_from_world.row(2);
393
394 if matches!(clusters.clusterable_objects, ClusterableObjects::Cpu(..)) {
395 cluster_aabb_spheres.clear();
397 cluster_aabb_spheres.extend(core::iter::repeat_n(None, cluster_count));
398
399 let mut x_planes = Vec::with_capacity(clusters.dimensions.x as usize + 1);
401 let mut y_planes = Vec::with_capacity(clusters.dimensions.y as usize + 1);
402 let mut z_planes = Vec::with_capacity(clusters.dimensions.z as usize + 1);
403
404 if is_orthographic {
405 let x_slices = clusters.dimensions.x as f32;
406 for x in 0..=clusters.dimensions.x {
407 let x_proportion = x as f32 / x_slices;
408 let x_pos = x_proportion * 2.0 - 1.0;
409 let view_x = clip_to_view(view_from_clip, Vec4::new(x_pos, 0.0, 1.0, 1.0)).x;
410 let normal = Vec3::X;
411 let d = view_x * normal.x;
412 x_planes.push(HalfSpace::new(normal.extend(d)));
413 }
414
415 let y_slices = clusters.dimensions.y as f32;
416 for y in 0..=clusters.dimensions.y {
417 let y_proportion = 1.0 - y as f32 / y_slices;
418 let y_pos = y_proportion * 2.0 - 1.0;
419 let view_y = clip_to_view(view_from_clip, Vec4::new(0.0, y_pos, 1.0, 1.0)).y;
420 let normal = Vec3::Y;
421 let d = view_y * normal.y;
422 y_planes.push(HalfSpace::new(normal.extend(d)));
423 }
424 } else {
425 let x_slices = clusters.dimensions.x as f32;
426 for x in 0..=clusters.dimensions.x {
427 let x_proportion = x as f32 / x_slices;
428 let x_pos = x_proportion * 2.0 - 1.0;
429 let nb = clip_to_view(view_from_clip, Vec4::new(x_pos, -1.0, 1.0, 1.0)).xyz();
430 let nt = clip_to_view(view_from_clip, Vec4::new(x_pos, 1.0, 1.0, 1.0)).xyz();
431 let normal = nb.cross(nt);
432 let d = nb.dot(normal);
433 x_planes.push(HalfSpace::new(normal.extend(d)));
434 }
435
436 let y_slices = clusters.dimensions.y as f32;
437 for y in 0..=clusters.dimensions.y {
438 let y_proportion = 1.0 - y as f32 / y_slices;
439 let y_pos = y_proportion * 2.0 - 1.0;
440 let nl = clip_to_view(view_from_clip, Vec4::new(-1.0, y_pos, 1.0, 1.0)).xyz();
441 let nr = clip_to_view(view_from_clip, Vec4::new(1.0, y_pos, 1.0, 1.0)).xyz();
442 let normal = nr.cross(nl);
443 let d = nr.dot(normal);
444 y_planes.push(HalfSpace::new(normal.extend(d)));
445 }
446 }
447
448 let z_slices = clusters.dimensions.z;
449 for z in 0..=z_slices {
450 let view_z =
451 z_slice_to_view_z(first_slice_depth, far_z, z_slices, z, is_orthographic);
452 let normal = -Vec3::Z;
453 let d = view_z * normal.z;
454 z_planes.push(HalfSpace::new(normal.extend(d)));
455 }
456
457 for clusterable_object in &clusterable_objects {
458 if !view_layers.intersects(&clusterable_object.render_layers) {
460 continue;
461 }
462
463 let clusterable_object_sphere = clusterable_object.sphere();
464
465 if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
467 continue;
468 }
469
470 let (
472 clusterable_object_aabb_xy_ndc_z_view_min,
473 clusterable_object_aabb_xy_ndc_z_view_max,
474 ) = cluster_space_clusterable_object_aabb(
475 view_from_world,
476 view_from_world_scale,
477 camera.clip_from_view(),
478 &clusterable_object_sphere,
479 );
480
481 let min_cluster = ndc_position_to_cluster(
482 clusters.dimensions,
483 cluster_factors,
484 is_orthographic,
485 clusterable_object_aabb_xy_ndc_z_view_min,
486 clusterable_object_aabb_xy_ndc_z_view_min.z,
487 );
488 let max_cluster = ndc_position_to_cluster(
489 clusters.dimensions,
490 cluster_factors,
491 is_orthographic,
492 clusterable_object_aabb_xy_ndc_z_view_max,
493 clusterable_object_aabb_xy_ndc_z_view_max.z,
494 );
495 let (min_cluster, max_cluster) =
496 (min_cluster.min(max_cluster), min_cluster.max(max_cluster));
497
498 let clusterable_objects = match clusters.clusterable_objects {
502 ClusterableObjects::Gpu => {
503 error!(
504 "We shouldn't be clustering objects on CPU if we're in GPU clustering \
505 mode"
506 );
507 return;
508 }
509 ClusterableObjects::Cpu(ref mut clusterable_objects_cpu) => {
510 clusterable_objects_cpu
511 }
512 };
513
514 let view_clusterable_object_sphere = Sphere {
522 center: Vec3A::from_vec4(
523 view_from_world * clusterable_object_sphere.center.extend(1.0),
524 ),
525 radius: clusterable_object_sphere.radius * view_from_world_scale_max,
526 };
527
528 let this_object_far_z = -view_from_world_row_2
529 .dot(clusterable_object.transform.translation().extend(1.0))
530 + clusterable_object.range * view_from_world_scale.z;
531 farthest_z = farthest_z.max(this_object_far_z);
532
533 let spot_light_dir_sin_cos = match clusterable_object.object_type {
534 ClusterableObjectType::SpotLight { outer_angle, .. } => {
535 let (angle_sin, angle_cos) = sin_cos(outer_angle);
536 Some((
537 (view_from_world * clusterable_object.transform.back().extend(0.0))
538 .truncate()
539 .normalize(),
540 angle_sin,
541 angle_cos,
542 ))
543 }
544 ClusterableObjectType::Decal => {
545 None
547 }
548 ClusterableObjectType::PointLight { .. }
549 | ClusterableObjectType::ReflectionProbe
550 | ClusterableObjectType::IrradianceVolume => None,
551 };
552 let clusterable_object_center_clip =
553 camera.clip_from_view() * view_clusterable_object_sphere.center.extend(1.0);
554 let object_center_ndc =
555 clusterable_object_center_clip.xyz() / clusterable_object_center_clip.w;
556 let cluster_coordinates = ndc_position_to_cluster(
557 clusters.dimensions,
558 cluster_factors,
559 is_orthographic,
560 object_center_ndc,
561 view_clusterable_object_sphere.center.z,
562 );
563 let z_center = if object_center_ndc.z <= 1.0 {
564 Some(cluster_coordinates.z)
565 } else {
566 None
567 };
568 let y_center = if object_center_ndc.y > 1.0 {
569 None
570 } else if object_center_ndc.y < -1.0 {
571 Some(clusters.dimensions.y + 1)
572 } else {
573 Some(cluster_coordinates.y)
574 };
575 for z in min_cluster.z..=max_cluster.z {
576 let mut z_object = view_clusterable_object_sphere;
577 if z_center.is_none() || z != z_center.unwrap() {
578 let z_plane = if z_center.is_some() && z < z_center.unwrap() {
582 z_planes[(z + 1) as usize]
583 } else {
584 z_planes[z as usize]
585 };
586 if let Some(projected) = project_to_plane_z(z_object, z_plane) {
589 z_object = projected;
590 } else {
591 continue;
592 }
593 }
594 for y in min_cluster.y..=max_cluster.y {
595 let mut y_object = z_object;
596 if y_center.is_none() || y != y_center.unwrap() {
597 let y_plane = if y_center.is_some() && y < y_center.unwrap() {
601 y_planes[(y + 1) as usize]
602 } else {
603 y_planes[y as usize]
604 };
605 if let Some(projected) =
608 project_to_plane_y(y_object, y_plane, is_orthographic)
609 {
610 y_object = projected;
611 } else {
612 continue;
613 }
614 }
615 let mut min_x = min_cluster.x;
617 loop {
618 if min_x >= max_cluster.x
619 || -get_distance_x(
620 x_planes[(min_x + 1) as usize],
621 y_object.center,
622 is_orthographic,
623 ) + y_object.radius
624 > 0.0
625 {
626 break;
627 }
628 min_x += 1;
629 }
630 let mut max_x = max_cluster.x;
632 loop {
633 if max_x <= min_x
634 || get_distance_x(
635 x_planes[max_x as usize],
636 y_object.center,
637 is_orthographic,
638 ) + y_object.radius
639 > 0.0
640 {
641 break;
642 }
643 max_x -= 1;
644 }
645 let mut cluster_index = ((y * clusters.dimensions.x + min_x)
646 * clusters.dimensions.z
647 + z) as usize;
648
649 match clusterable_object.object_type {
650 ClusterableObjectType::SpotLight { .. } => {
651 let (view_light_direction, angle_sin, angle_cos) =
652 spot_light_dir_sin_cos.unwrap();
653 for x in min_x..=max_x {
654 let cluster_aabb_sphere =
657 &mut cluster_aabb_spheres[cluster_index];
658 let cluster_aabb_sphere =
659 if let Some(sphere) = cluster_aabb_sphere {
660 &*sphere
661 } else {
662 let aabb = compute_aabb_for_cluster(
663 first_slice_depth,
664 far_z,
665 clusters.tile_size.as_vec2(),
666 screen_size.as_vec2(),
667 view_from_clip,
668 is_orthographic,
669 clusters.dimensions,
670 UVec3::new(x, y, z),
671 );
672 let sphere = Sphere {
673 center: aabb.center,
674 radius: aabb.half_extents.length(),
675 };
676 *cluster_aabb_sphere = Some(sphere);
677 cluster_aabb_sphere.as_ref().unwrap()
678 };
679
680 let spot_light_offset = Vec3::from(
682 view_clusterable_object_sphere.center
683 - cluster_aabb_sphere.center,
684 );
685 let spot_light_dist_sq = spot_light_offset.length_squared();
686 let v1_len = spot_light_offset.dot(view_light_direction);
687
688 let distance_closest_point = (angle_cos
689 * (spot_light_dist_sq - v1_len * v1_len).sqrt())
690 - v1_len * angle_sin;
691 let angle_cull =
692 distance_closest_point > cluster_aabb_sphere.radius;
693
694 let front_cull = v1_len
695 > cluster_aabb_sphere.radius
696 + clusterable_object.range * view_from_world_scale_max;
697 let back_cull = v1_len < -cluster_aabb_sphere.radius;
698
699 if !angle_cull && !front_cull && !back_cull {
700 clusterable_objects[cluster_index]
702 .add_spot_light(clusterable_object.entity);
703 total_cluster_index_count += 1;
704 }
705 cluster_index += clusters.dimensions.z as usize;
706 }
707 }
708
709 ClusterableObjectType::PointLight { .. } => {
710 for _ in min_x..=max_x {
711 clusterable_objects[cluster_index]
713 .add_point_light(clusterable_object.entity);
714 cluster_index += clusters.dimensions.z as usize;
715 }
716 total_cluster_index_count += (max_x - min_x + 1) as usize;
717 }
718
719 ClusterableObjectType::ReflectionProbe => {
720 for _ in min_x..=max_x {
726 clusterable_objects[cluster_index]
727 .add_reflection_probe(clusterable_object.entity);
728 cluster_index += clusters.dimensions.z as usize;
729 }
730 total_cluster_index_count += (max_x - min_x + 1) as usize;
731 }
732
733 ClusterableObjectType::IrradianceVolume => {
734 for _ in min_x..=max_x {
740 clusterable_objects[cluster_index]
741 .add_irradiance_volume(clusterable_object.entity);
742 cluster_index += clusters.dimensions.z as usize;
743 }
744 total_cluster_index_count += (max_x - min_x + 1) as usize;
745 }
746
747 ClusterableObjectType::Decal => {
748 for _ in min_x..=max_x {
754 clusterable_objects[cluster_index]
755 .add_decal(clusterable_object.entity);
756 cluster_index += clusters.dimensions.z as usize;
757 }
758 total_cluster_index_count += (max_x - min_x + 1) as usize;
759 }
760 }
761 }
762 }
763 }
764 }
765
766 clusters.last_frame_total_cluster_index_count = Some(total_cluster_index_count);
770 clusters.last_frame_farthest_z = Some(farthest_z);
771 }
772}
773
774#[expect(missing_docs, reason = "TODO")]
776pub fn calculate_cluster_factors(
777 near: f32,
778 far: f32,
779 z_slices: f32,
780 is_orthographic: bool,
781) -> Vec2 {
782 if is_orthographic {
783 Vec2::new(-near, z_slices / (-far - -near))
784 } else {
785 let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near);
786 Vec2::new(
787 z_slices_of_ln_zfar_over_znear,
788 ops::ln(near) * z_slices_of_ln_zfar_over_znear,
789 )
790 }
791}
792
793fn compute_aabb_for_cluster(
794 z_near: f32,
795 z_far: f32,
796 tile_size: Vec2,
797 screen_size: Vec2,
798 view_from_clip: Mat4,
799 is_orthographic: bool,
800 cluster_dimensions: UVec3,
801 ijk: UVec3,
802) -> Aabb {
803 let ijk = ijk.as_vec3();
804
805 let p_min = ijk.xy() * tile_size;
807 let p_max = p_min + tile_size;
808
809 let cluster_min;
810 let cluster_max;
811 if is_orthographic {
812 let mut p_min = screen_to_view(screen_size, view_from_clip, p_min, 0.0).xyz();
817 let mut p_max = screen_to_view(screen_size, view_from_clip, p_max, 0.0).xyz();
818
819 p_min.z = -z_near + (z_near - z_far) * ijk.z / cluster_dimensions.z as f32;
821 p_max.z = -z_near + (z_near - z_far) * (ijk.z + 1.0) / cluster_dimensions.z as f32;
822
823 cluster_min = p_min.min(p_max);
824 cluster_max = p_min.max(p_max);
825 } else {
826 let p_min = screen_to_view(screen_size, view_from_clip, p_min, 1.0);
829 let p_max = screen_to_view(screen_size, view_from_clip, p_max, 1.0);
830
831 let z_far_over_z_near = -z_far / -z_near;
832 let cluster_near = if ijk.z == 0.0 {
833 0.0
834 } else {
835 -z_near
836 * ops::powf(
837 z_far_over_z_near,
838 (ijk.z - 1.0) / (cluster_dimensions.z - 1) as f32,
839 )
840 };
841 let cluster_far = if cluster_dimensions.z == 1 {
844 -z_far
845 } else {
846 -z_near * ops::powf(z_far_over_z_near, ijk.z / (cluster_dimensions.z - 1) as f32)
847 };
848
849 let p_min_near = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_near);
851 let p_min_far = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_far);
852 let p_max_near = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_near);
853 let p_max_far = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_far);
854
855 cluster_min = p_min_near.min(p_min_far).min(p_max_near.min(p_max_far));
856 cluster_max = p_min_near.max(p_min_far).max(p_max_near.max(p_max_far));
857 }
858
859 Aabb::from_min_max(cluster_min, cluster_max)
860}
861
862fn z_slice_to_view_z(
864 near: f32,
865 far: f32,
866 z_slices: u32,
867 z_slice: u32,
868 is_orthographic: bool,
869) -> f32 {
870 if is_orthographic {
871 return -near - (far - near) * z_slice as f32 / z_slices as f32;
872 }
873
874 if z_slice == 0 {
876 0.0
877 } else {
878 -near * ops::powf(far / near, (z_slice - 1) as f32 / (z_slices - 1) as f32)
879 }
880}
881
882fn ndc_position_to_cluster(
883 cluster_dimensions: UVec3,
884 cluster_factors: Vec2,
885 is_orthographic: bool,
886 ndc_p: Vec3,
887 view_z: f32,
888) -> UVec3 {
889 let cluster_dimensions_f32 = cluster_dimensions.as_vec3();
890 let frag_coord = (ndc_p.xy() * VEC2_HALF_NEGATIVE_Y + VEC2_HALF).clamp(Vec2::ZERO, Vec2::ONE);
891 let xy = (frag_coord * cluster_dimensions_f32.xy()).floor();
892 let z_slice = view_z_to_z_slice(
893 cluster_factors,
894 cluster_dimensions.z,
895 view_z,
896 is_orthographic,
897 );
898 xy.as_uvec2()
899 .extend(z_slice)
900 .clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE)
901}
902
903fn cluster_space_clusterable_object_aabb(
909 view_from_world: Mat4,
910 view_from_world_scale: Vec3,
911 clip_from_view: Mat4,
912 clusterable_object_sphere: &Sphere,
913) -> (Vec3, Vec3) {
914 let clusterable_object_aabb_view = Aabb {
915 center: Vec3A::from_vec4(view_from_world * clusterable_object_sphere.center.extend(1.0)),
916 half_extents: Vec3A::from(clusterable_object_sphere.radius * view_from_world_scale.abs()),
917 };
918 let (mut clusterable_object_aabb_view_min, mut clusterable_object_aabb_view_max) = (
919 clusterable_object_aabb_view.min(),
920 clusterable_object_aabb_view.max(),
921 );
922
923 clusterable_object_aabb_view_min.z = clusterable_object_aabb_view_min.z.min(-f32::MIN_POSITIVE);
931 clusterable_object_aabb_view_max.z = clusterable_object_aabb_view_max.z.min(-f32::MIN_POSITIVE);
932
933 let (
938 clusterable_object_aabb_view_xymin_near,
939 clusterable_object_aabb_view_xymin_far,
940 clusterable_object_aabb_view_xymax_near,
941 clusterable_object_aabb_view_xymax_far,
942 ) = (
943 clusterable_object_aabb_view_min,
944 clusterable_object_aabb_view_min
945 .xy()
946 .extend(clusterable_object_aabb_view_max.z),
947 clusterable_object_aabb_view_max
948 .xy()
949 .extend(clusterable_object_aabb_view_min.z),
950 clusterable_object_aabb_view_max,
951 );
952 let (
953 clusterable_object_aabb_clip_xymin_near,
954 clusterable_object_aabb_clip_xymin_far,
955 clusterable_object_aabb_clip_xymax_near,
956 clusterable_object_aabb_clip_xymax_far,
957 ) = (
958 clip_from_view * clusterable_object_aabb_view_xymin_near.extend(1.0),
959 clip_from_view * clusterable_object_aabb_view_xymin_far.extend(1.0),
960 clip_from_view * clusterable_object_aabb_view_xymax_near.extend(1.0),
961 clip_from_view * clusterable_object_aabb_view_xymax_far.extend(1.0),
962 );
963 let (
964 clusterable_object_aabb_ndc_xymin_near,
965 clusterable_object_aabb_ndc_xymin_far,
966 clusterable_object_aabb_ndc_xymax_near,
967 clusterable_object_aabb_ndc_xymax_far,
968 ) = (
969 clusterable_object_aabb_clip_xymin_near.xyz() / clusterable_object_aabb_clip_xymin_near.w,
970 clusterable_object_aabb_clip_xymin_far.xyz() / clusterable_object_aabb_clip_xymin_far.w,
971 clusterable_object_aabb_clip_xymax_near.xyz() / clusterable_object_aabb_clip_xymax_near.w,
972 clusterable_object_aabb_clip_xymax_far.xyz() / clusterable_object_aabb_clip_xymax_far.w,
973 );
974 let (clusterable_object_aabb_ndc_min, clusterable_object_aabb_ndc_max) = (
975 clusterable_object_aabb_ndc_xymin_near
976 .min(clusterable_object_aabb_ndc_xymin_far)
977 .min(clusterable_object_aabb_ndc_xymax_near)
978 .min(clusterable_object_aabb_ndc_xymax_far),
979 clusterable_object_aabb_ndc_xymin_near
980 .max(clusterable_object_aabb_ndc_xymin_far)
981 .max(clusterable_object_aabb_ndc_xymax_near)
982 .max(clusterable_object_aabb_ndc_xymax_far),
983 );
984
985 let (aabb_min_ndc, aabb_max_ndc) = (
987 clusterable_object_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX),
988 clusterable_object_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX),
989 );
990
991 (
993 aabb_min_ndc.extend(clusterable_object_aabb_view_min.z),
994 aabb_max_ndc.extend(clusterable_object_aabb_view_max.z),
995 )
996}
997
998fn line_intersection_to_z_plane(origin: Vec3, p: Vec3, z: f32) -> Vec3 {
1000 let v = p - origin;
1001 let t = (z - Vec3::Z.dot(origin)) / Vec3::Z.dot(v);
1002 origin + t * v
1003}
1004
1005fn view_z_to_z_slice(
1007 cluster_factors: Vec2,
1008 z_slices: u32,
1009 view_z: f32,
1010 is_orthographic: bool,
1011) -> u32 {
1012 let z_slice = if is_orthographic {
1013 ((view_z - cluster_factors.x) * cluster_factors.y).floor() as u32
1015 } else {
1016 (ops::ln(-view_z) * cluster_factors.x - cluster_factors.y + 1.0) as u32
1018 };
1019 z_slice.min(z_slices - 1)
1022}
1023
1024fn clip_to_view(view_from_clip: Mat4, clip: Vec4) -> Vec4 {
1025 let view = view_from_clip * clip;
1026 view / view.w
1027}
1028
1029fn screen_to_view(screen_size: Vec2, view_from_clip: Mat4, screen: Vec2, ndc_z: f32) -> Vec4 {
1030 let tex_coord = screen / screen_size;
1031 let clip = Vec4::new(
1032 tex_coord.x * 2.0 - 1.0,
1033 (1.0 - tex_coord.y) * 2.0 - 1.0,
1034 ndc_z,
1035 1.0,
1036 );
1037 clip_to_view(view_from_clip, clip)
1038}
1039
1040fn get_distance_x(plane: HalfSpace, point: Vec3A, is_orthographic: bool) -> f32 {
1042 if is_orthographic {
1043 point.x - plane.d()
1044 } else {
1045 plane.normal_d().xz().dot(point.xz())
1050 }
1051}
1052
1053fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
1055 let z = z_plane.d() / z_plane.normal_d().z;
1063 let distance_to_plane = z - z_object.center.z;
1064 if distance_to_plane.abs() > z_object.radius {
1065 return None;
1066 }
1067 Some(Sphere {
1068 center: Vec3A::from(z_object.center.xy().extend(z)),
1069 radius: (z_object.radius * z_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1072 })
1073}
1074
1075fn project_to_plane_y(
1077 y_object: Sphere,
1078 y_plane: HalfSpace,
1079 is_orthographic: bool,
1080) -> Option<Sphere> {
1081 let distance_to_plane = if is_orthographic {
1082 y_plane.d() - y_object.center.y
1083 } else {
1084 -y_object.center.yz().dot(y_plane.normal_d().yz())
1085 };
1086
1087 if distance_to_plane.abs() > y_object.radius {
1088 return None;
1089 }
1090 Some(Sphere {
1091 center: y_object.center + distance_to_plane * y_plane.normal(),
1092 radius: (y_object.radius * y_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1093 })
1094}