1use crate::compute::ComputeKernel;
7use std::collections::HashMap;
8
9pub struct AabbSortKernel;
20
21impl ComputeKernel for AabbSortKernel {
22 fn name(&self) -> &str {
23 "AabbSortKernel"
24 }
25
26 fn execute(&self, inputs: &[&[f64]], outputs: &mut [Vec<f64>], _work_size: usize) {
27 if inputs.is_empty() || outputs.is_empty() {
28 return;
29 }
30 let aabbs = inputs[0];
31 let n = aabbs.len() / 6;
32 let mut indices: Vec<usize> = (0..n).collect();
34 indices.sort_by(|&a, &b| {
35 let ax = aabbs[a * 6];
36 let bx = aabbs[b * 6];
37 ax.partial_cmp(&bx).unwrap_or(std::cmp::Ordering::Equal)
38 });
39 outputs[0] = indices.iter().map(|&i| i as f64).collect();
40 }
41}
42
43pub struct AabbOverlapKernel;
50
51impl AabbOverlapKernel {
52 #[inline]
53 fn overlaps(a: &[f64], b: &[f64]) -> bool {
54 a[0] <= b[1] && a[1] >= b[0] && a[2] <= b[3] && a[3] >= b[2] && a[4] <= b[5] && a[5] >= b[4] }
59}
60
61impl ComputeKernel for AabbOverlapKernel {
62 fn name(&self) -> &str {
63 "AabbOverlapKernel"
64 }
65
66 fn execute(&self, inputs: &[&[f64]], outputs: &mut [Vec<f64>], _work_size: usize) {
67 if inputs.is_empty() || outputs.is_empty() {
68 return;
69 }
70 let aabbs = inputs[0];
71 let n = aabbs.len() / 6;
72 let mut pairs = Vec::new();
73 for i in 0..n {
74 for j in (i + 1)..n {
75 if Self::overlaps(&aabbs[i * 6..(i + 1) * 6], &aabbs[j * 6..(j + 1) * 6]) {
76 pairs.push(i as f64);
77 pairs.push(j as f64);
78 }
79 }
80 }
81 outputs[0] = pairs;
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq)]
91pub struct AabbGpu {
92 pub min: [f32; 3],
94 pub max: [f32; 3],
96 pub body_id: u32,
98}
99
100impl AabbGpu {
101 pub fn new(min: [f32; 3], max: [f32; 3], body_id: u32) -> Self {
103 Self { min, max, body_id }
104 }
105
106 #[inline]
108 pub fn overlaps(&self, other: &AabbGpu) -> bool {
109 self.min[0] <= other.max[0]
110 && self.max[0] >= other.min[0]
111 && self.min[1] <= other.max[1]
112 && self.max[1] >= other.min[1]
113 && self.min[2] <= other.max[2]
114 && self.max[2] >= other.min[2]
115 }
116}
117
118pub struct SortAndSweepGpu;
127
128impl SortAndSweepGpu {
129 pub fn detect_pairs(aabbs: &[AabbGpu]) -> Vec<(u32, u32)> {
135 if aabbs.is_empty() {
136 return Vec::new();
137 }
138
139 let mut sorted: Vec<&AabbGpu> = aabbs.iter().collect();
141 sorted.sort_by(|a, b| {
142 a.min[0]
143 .partial_cmp(&b.min[0])
144 .unwrap_or(std::cmp::Ordering::Equal)
145 });
146
147 let n = sorted.len();
148 let mut pairs = Vec::new();
149
150 for i in 0..n {
151 for j in (i + 1)..n {
152 if sorted[j].min[0] > sorted[i].max[0] {
154 break;
155 }
156 if sorted[i].overlaps(sorted[j]) {
157 let a = sorted[i].body_id;
158 let b = sorted[j].body_id;
159 let pair = if a < b { (a, b) } else { (b, a) };
160 pairs.push(pair);
161 }
162 }
163 }
164
165 pairs
166 }
167}
168
169#[derive(Debug, Clone)]
175pub struct UniformGridGpu {
176 pub cell_size: f32,
178 pub origin: [f32; 3],
180 pub dims: [u32; 3],
182}
183
184impl UniformGridGpu {
185 pub fn new(cell_size: f32, origin: [f32; 3], dims: [u32; 3]) -> Self {
187 Self {
188 cell_size,
189 origin,
190 dims,
191 }
192 }
193
194 pub fn cell_of(&self, pos: [f32; 3]) -> [i32; 3] {
196 [
197 ((pos[0] - self.origin[0]) / self.cell_size).floor() as i32,
198 ((pos[1] - self.origin[1]) / self.cell_size).floor() as i32,
199 ((pos[2] - self.origin[2]) / self.cell_size).floor() as i32,
200 ]
201 }
202
203 fn cell_key(ix: i32, iy: i32, iz: i32) -> u64 {
205 let x = (ix as i16) as u64 & 0xFFFF;
207 let y = (iy as i16) as u64 & 0xFFFF;
208 let z = (iz as i16) as u64 & 0xFFFF;
209 (z << 32) | (y << 16) | x
210 }
211
212 pub fn insert_aabbs(&self, aabbs: &[AabbGpu]) -> HashMap<u64, Vec<u32>> {
217 let mut map: HashMap<u64, Vec<u32>> = HashMap::new();
218
219 for aabb in aabbs {
220 let min_cell = self.cell_of(aabb.min);
221 let max_cell = self.cell_of(aabb.max);
222
223 for iz in min_cell[2]..=max_cell[2] {
224 for iy in min_cell[1]..=max_cell[1] {
225 for ix in min_cell[0]..=max_cell[0] {
226 let key = Self::cell_key(ix, iy, iz);
227 map.entry(key).or_default().push(aabb.body_id);
228 }
229 }
230 }
231 }
232
233 map
234 }
235
236 pub fn query_pairs(&self, aabbs: &[AabbGpu]) -> Vec<(u32, u32)> {
240 let map = self.insert_aabbs(aabbs);
241
242 let mut seen = std::collections::HashSet::new();
243 let mut pairs = Vec::new();
244
245 for body_list in map.values() {
246 let n = body_list.len();
247 for i in 0..n {
248 for j in (i + 1)..n {
249 let a = body_list[i];
250 let b = body_list[j];
251 let pair = if a < b { (a, b) } else { (b, a) };
252 if seen.insert(pair) {
253 if let (Some(aa), Some(bb)) = (
255 aabbs.iter().find(|x| x.body_id == pair.0),
256 aabbs.iter().find(|x| x.body_id == pair.1),
257 ) && aa.overlaps(bb)
258 {
259 pairs.push(pair);
260 }
261 }
262 }
263 }
264 }
265
266 pairs.sort();
267 pairs
268 }
269}
270
271pub fn morton_code(x: u32, y: u32, z: u32) -> u64 {
280 spread_bits(x as u64) | (spread_bits(y as u64) << 1) | (spread_bits(z as u64) << 2)
281}
282
283#[inline]
285fn spread_bits(mut v: u64) -> u64 {
286 v &= 0x1fffff; v = (v | (v << 32)) & 0x1f00000000ffff;
288 v = (v | (v << 16)) & 0x1f0000ff0000ff;
289 v = (v | (v << 8)) & 0x100f00f00f00f00f;
290 v = (v | (v << 4)) & 0x10c30c30c30c30c3;
291 v = (v | (v << 2)) & 0x1249249249249249;
292 v
293}
294
295pub fn sort_and_sweep_flat(aabbs: &[f64]) -> Vec<(usize, usize)> {
304 let n = aabbs.len() / 6;
305 if n == 0 {
306 return Vec::new();
307 }
308
309 let mut order: Vec<usize> = (0..n).collect();
311 order.sort_by(|&a, &b| {
312 let ax = aabbs[a * 6];
313 let bx = aabbs[b * 6];
314 ax.partial_cmp(&bx).unwrap_or(std::cmp::Ordering::Equal)
315 });
316
317 let mut pairs = Vec::new();
318 for (i, &si) in order.iter().enumerate() {
319 let max_x_i = aabbs[si * 6 + 1];
320 for &sj in order.iter().skip(i + 1) {
321 if aabbs[sj * 6] > max_x_i {
322 break; }
324 let ai = &aabbs[si * 6..(si + 1) * 6];
326 let aj = &aabbs[sj * 6..(sj + 1) * 6];
327 if ai[0] <= aj[1]
328 && ai[1] >= aj[0]
329 && ai[2] <= aj[3]
330 && ai[3] >= aj[2]
331 && ai[4] <= aj[5]
332 && ai[5] >= aj[4]
333 {
334 let pair = if si < sj { (si, sj) } else { (sj, si) };
335 pairs.push(pair);
336 }
337 }
338 }
339 pairs.sort();
340 pairs.dedup();
341 pairs
342}
343
344pub fn assign_to_grid_cells(aabbs: &[f64], cell_size: f64, origin: [f64; 3]) -> Vec<(u64, usize)> {
352 let n = aabbs.len() / 6;
353 let mut result = Vec::new();
354 let pack = |ix: i64, iy: i64, iz: i64| -> u64 {
355 let x = (ix as i16) as u64 & 0xFFFF;
356 let y = (iy as i16) as u64 & 0xFFFF;
357 let z = (iz as i16) as u64 & 0xFFFF;
358 (z << 32) | (y << 16) | x
359 };
360 for i in 0..n {
361 let a = &aabbs[i * 6..(i + 1) * 6];
362 let ix0 = ((a[0] - origin[0]) / cell_size).floor() as i64;
363 let ix1 = ((a[1] - origin[0]) / cell_size).floor() as i64;
364 let iy0 = ((a[2] - origin[1]) / cell_size).floor() as i64;
365 let iy1 = ((a[3] - origin[1]) / cell_size).floor() as i64;
366 let iz0 = ((a[4] - origin[2]) / cell_size).floor() as i64;
367 let iz1 = ((a[5] - origin[2]) / cell_size).floor() as i64;
368 for iz in iz0..=iz1 {
369 for iy in iy0..=iy1 {
370 for ix in ix0..=ix1 {
371 result.push((pack(ix, iy, iz), i));
372 }
373 }
374 }
375 }
376 result
377}
378
379pub fn pairs_from_grid_assignments(assignments: &[(u64, usize)]) -> Vec<(usize, usize)> {
383 let mut by_cell: HashMap<u64, Vec<usize>> = HashMap::new();
384 for &(key, idx) in assignments {
385 by_cell.entry(key).or_default().push(idx);
386 }
387 let mut seen = std::collections::HashSet::new();
388 let mut pairs = Vec::new();
389 for bodies in by_cell.values() {
390 let n = bodies.len();
391 for i in 0..n {
392 for j in (i + 1)..n {
393 let a = bodies[i];
394 let b = bodies[j];
395 let p = if a < b { (a, b) } else { (b, a) };
396 if seen.insert(p) {
397 pairs.push(p);
398 }
399 }
400 }
401 }
402 pairs.sort();
403 pairs
404}
405
406pub fn morton_key_for_aabb(aabb: &AabbGpu, cell_size: f32, origin: [f32; 3]) -> u64 {
414 let cx = ((aabb.min[0] + aabb.max[0]) * 0.5 - origin[0]) / cell_size;
415 let cy = ((aabb.min[1] + aabb.max[1]) * 0.5 - origin[1]) / cell_size;
416 let cz = ((aabb.min[2] + aabb.max[2]) * 0.5 - origin[2]) / cell_size;
417
418 let ix = (cx.max(0.0) as u32).min(0x1F_FFFF);
419 let iy = (cy.max(0.0) as u32).min(0x1F_FFFF);
420 let iz = (cz.max(0.0) as u32).min(0x1F_FFFF);
421 morton_code(ix, iy, iz)
422}
423
424pub fn morton_sort(aabbs: &[AabbGpu], cell_size: f32, origin: [f32; 3]) -> Vec<AabbGpu> {
428 let mut keyed: Vec<(u64, AabbGpu)> = aabbs
429 .iter()
430 .map(|a| (morton_key_for_aabb(a, cell_size, origin), *a))
431 .collect();
432 keyed.sort_by_key(|&(k, _)| k);
433 keyed.into_iter().map(|(_, a)| a).collect()
434}
435
436#[derive(Debug, Clone, Default)]
442pub struct CompactPairList {
443 pairs: Vec<(u32, u32)>,
444}
445
446impl CompactPairList {
447 pub fn new() -> Self {
449 Self::default()
450 }
451
452 pub fn insert(&mut self, a: u32, b: u32) {
454 let pair = if a < b { (a, b) } else { (b, a) };
455 if !self.pairs.contains(&pair) {
457 self.pairs.push(pair);
458 }
459 }
460
461 pub fn insert_all(&mut self, pairs: &[(u32, u32)]) {
463 for &(a, b) in pairs {
464 self.insert(a, b);
465 }
466 }
467
468 pub fn sort(&mut self) {
470 self.pairs.sort();
471 }
472
473 pub fn pairs(&self) -> &[(u32, u32)] {
475 &self.pairs
476 }
477
478 pub fn len(&self) -> usize {
480 self.pairs.len()
481 }
482
483 pub fn is_empty(&self) -> bool {
485 self.pairs.is_empty()
486 }
487
488 pub fn remove_body(&mut self, body_id: u32) {
490 self.pairs.retain(|&(a, b)| a != body_id && b != body_id);
491 }
492
493 pub fn contains(&self, a: u32, b: u32) -> bool {
495 let p = if a < b { (a, b) } else { (b, a) };
496 self.pairs.contains(&p)
497 }
498}
499
500#[derive(Debug, Clone, Copy)]
506pub struct BvhGpuNode {
507 pub aabb: AabbGpu,
509 pub left: i32,
511 pub right: i32,
513}
514
515impl BvhGpuNode {
516 pub fn internal(aabb: AabbGpu, left: i32, right: i32) -> Self {
518 Self { aabb, left, right }
519 }
520
521 pub fn leaf(aabb: AabbGpu) -> Self {
523 let id = aabb.body_id as i32;
524 Self {
525 aabb,
526 left: -(id + 1),
527 right: -(id + 1),
528 }
529 }
530
531 pub fn is_leaf(&self) -> bool {
533 self.left < 0
534 }
535}
536
537pub fn build_bvh(aabbs: &[AabbGpu]) -> Vec<BvhGpuNode> {
542 let mut nodes = Vec::new();
543 if aabbs.is_empty() {
544 return nodes;
545 }
546 let mut indices: Vec<usize> = (0..aabbs.len()).collect();
547 build_bvh_recursive(aabbs, &mut indices, &mut nodes);
548 nodes
549}
550
551fn merge_aabbs(aabbs: &[AabbGpu], indices: &[usize]) -> AabbGpu {
552 let mut min = aabbs[indices[0]].min;
553 let mut max = aabbs[indices[0]].max;
554 for &idx in &indices[1..] {
555 let a = &aabbs[idx];
556 for k in 0..3 {
557 if a.min[k] < min[k] {
558 min[k] = a.min[k];
559 }
560 if a.max[k] > max[k] {
561 max[k] = a.max[k];
562 }
563 }
564 }
565 AabbGpu {
566 min,
567 max,
568 body_id: 0,
569 }
570}
571
572fn build_bvh_recursive(
573 aabbs: &[AabbGpu],
574 indices: &mut [usize],
575 nodes: &mut Vec<BvhGpuNode>,
576) -> i32 {
577 let n = indices.len();
578 let merged = merge_aabbs(aabbs, indices);
579
580 if n == 1 {
581 let idx = nodes.len() as i32;
582 nodes.push(BvhGpuNode::leaf(aabbs[indices[0]]));
583 return idx;
584 }
585
586 let extents = [
588 merged.max[0] - merged.min[0],
589 merged.max[1] - merged.min[1],
590 merged.max[2] - merged.min[2],
591 ];
592 let axis = if extents[0] >= extents[1] && extents[0] >= extents[2] {
593 0
594 } else if extents[1] >= extents[2] {
595 1
596 } else {
597 2
598 };
599
600 indices.sort_by(|&a, &b| {
602 let ca = (aabbs[a].min[axis] + aabbs[a].max[axis]) * 0.5;
603 let cb = (aabbs[b].min[axis] + aabbs[b].max[axis]) * 0.5;
604 ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
605 });
606
607 let mid = n / 2;
608 let (left_idx, right_idx) = indices.split_at_mut(mid);
609
610 let node_idx = nodes.len() as i32;
612 nodes.push(BvhGpuNode {
613 aabb: merged,
614 left: 0,
615 right: 0,
616 }); let mut left_indices = left_idx.to_vec();
619 let mut right_indices = right_idx.to_vec();
620
621 let left = build_bvh_recursive(aabbs, &mut left_indices, nodes);
622 let right = build_bvh_recursive(aabbs, &mut right_indices, nodes);
623
624 nodes[node_idx as usize].left = left;
625 nodes[node_idx as usize].right = right;
626
627 node_idx
628}
629
630pub fn lbvh_query_pairs(nodes: &[BvhGpuNode]) -> Vec<(u32, u32)> {
639 if nodes.is_empty() {
640 return Vec::new();
641 }
642 let mut pairs = Vec::new();
643 let mut stack: Vec<(usize, usize)> = Vec::new();
645 stack.push((0, 0));
647
648 while let Some((a_idx, b_idx)) = stack.pop() {
649 let na = &nodes[a_idx];
650 let nb = &nodes[b_idx];
651
652 if !na.aabb.overlaps(&nb.aabb) {
653 continue;
654 }
655
656 if na.is_leaf() && nb.is_leaf() {
657 if a_idx != b_idx {
658 let id_a = na.aabb.body_id;
659 let id_b = nb.aabb.body_id;
660 let pair = if id_a < id_b {
661 (id_a, id_b)
662 } else {
663 (id_b, id_a)
664 };
665 pairs.push(pair);
666 }
667 continue;
668 }
669
670 if na.is_leaf() {
672 if nb.left >= 0 {
674 stack.push((a_idx, nb.left as usize));
675 }
676 if nb.right >= 0 {
677 stack.push((a_idx, nb.right as usize));
678 }
679 } else if nb.is_leaf() {
680 if na.left >= 0 {
682 stack.push((na.left as usize, b_idx));
683 }
684 if na.right >= 0 {
685 stack.push((na.right as usize, b_idx));
686 }
687 } else {
688 if na.left >= 0 {
690 stack.push((na.left as usize, b_idx));
691 }
692 if na.right >= 0 {
693 stack.push((na.right as usize, b_idx));
694 }
695 }
696 }
697 pairs.sort();
698 pairs.dedup();
699 pairs
700}
701
702pub fn refit_bvh(nodes: &mut [BvhGpuNode]) {
712 let n = nodes.len();
714 for i in (0..n).rev() {
715 if nodes[i].is_leaf() {
716 continue; }
718 let left_idx = nodes[i].left;
719 let right_idx = nodes[i].right;
720 if left_idx < 0 || right_idx < 0 {
721 continue;
722 }
723 let l = &nodes[left_idx as usize];
724 let r = &nodes[right_idx as usize];
725 let mut min = l.aabb.min;
726 let mut max = l.aabb.max;
727 for k in 0..3 {
728 if r.aabb.min[k] < min[k] {
729 min[k] = r.aabb.min[k];
730 }
731 if r.aabb.max[k] > max[k] {
732 max[k] = r.aabb.max[k];
733 }
734 }
735 nodes[i].aabb = AabbGpu {
736 min,
737 max,
738 body_id: 0,
739 };
740 }
741}
742
743pub fn aabb_surface_area(aabb: &AabbGpu) -> f32 {
749 let dx = aabb.max[0] - aabb.min[0];
750 let dy = aabb.max[1] - aabb.min[1];
751 let dz = aabb.max[2] - aabb.min[2];
752 2.0 * (dx * dy + dy * dz + dz * dx)
753}
754
755pub fn bvh_sah_cost(nodes: &[BvhGpuNode], cost_traversal: f32, cost_primitive: f32) -> f32 {
762 if nodes.is_empty() {
763 return 0.0;
764 }
765 let root_sa = aabb_surface_area(&nodes[0].aabb);
766 if root_sa < 1e-20 {
767 return 0.0;
768 }
769 let mut cost = 0.0f32;
770 for node in nodes {
771 let sa = aabb_surface_area(&node.aabb);
772 if node.is_leaf() {
773 cost += sa / root_sa * cost_primitive;
774 } else {
775 cost += sa / root_sa * cost_traversal;
776 }
777 }
778 cost
779}
780
781pub fn bvh_depth(nodes: &[BvhGpuNode]) -> usize {
785 if nodes.is_empty() {
786 return 0;
787 }
788 let mut max_depth = 0usize;
789 let mut stack: Vec<(usize, usize)> = vec![(0, 0)];
791 while let Some((idx, depth)) = stack.pop() {
792 if depth > max_depth {
793 max_depth = depth;
794 }
795 let node = &nodes[idx];
796 if !node.is_leaf() {
797 if node.left >= 0 {
798 stack.push((node.left as usize, depth + 1));
799 }
800 if node.right >= 0 {
801 stack.push((node.right as usize, depth + 1));
802 }
803 }
804 }
805 max_depth
806}
807
808pub fn bvh_leaf_count(nodes: &[BvhGpuNode]) -> usize {
810 nodes.iter().filter(|n| n.is_leaf()).count()
811}
812
813pub fn sap_incremental_update(
828 existing: &CompactPairList,
829 aabbs: &[AabbGpu],
830 moved_ids: &[u32],
831) -> CompactPairList {
832 let mut new_list = existing.clone();
833
834 for &id in moved_ids {
836 new_list.remove_body(id);
837 }
838
839 for &moved_id in moved_ids {
841 if let Some(moved_aabb) = aabbs.iter().find(|a| a.body_id == moved_id) {
842 for other in aabbs {
843 if other.body_id == moved_id {
844 continue;
845 }
846 if moved_aabb.overlaps(other) {
847 new_list.insert(moved_id, other.body_id);
848 }
849 }
850 }
851 }
852
853 new_list.sort();
854 new_list
855}
856
857pub fn sah_best_split(
868 aabbs: &[AabbGpu],
869 indices: &[usize],
870 axis: usize,
871 num_bins: usize,
872) -> Option<usize> {
873 if indices.len() < 2 || num_bins < 2 {
874 return None;
875 }
876
877 let min_c = indices
879 .iter()
880 .map(|&i| 0.5 * (aabbs[i].min[axis] + aabbs[i].max[axis]))
881 .fold(f32::INFINITY, f32::min);
882 let max_c = indices
883 .iter()
884 .map(|&i| 0.5 * (aabbs[i].min[axis] + aabbs[i].max[axis]))
885 .fold(f32::NEG_INFINITY, f32::max);
886
887 if (max_c - min_c).abs() < 1e-10 {
888 return None;
889 }
890
891 let bin_width = (max_c - min_c) / num_bins as f32;
892 let mut bin_counts = vec![0usize; num_bins];
893 let mut bin_aabbs: Vec<Option<AabbGpu>> = vec![None; num_bins];
894
895 for &i in indices {
896 let c = 0.5 * (aabbs[i].min[axis] + aabbs[i].max[axis]);
897 let bin = ((c - min_c) / bin_width).floor() as usize;
898 let bin = bin.min(num_bins - 1);
899 bin_counts[bin] += 1;
900 bin_aabbs[bin] = Some(match &bin_aabbs[bin] {
901 None => aabbs[i],
902 Some(prev) => {
903 let mut merged = *prev;
904 for k in 0..3 {
905 if aabbs[i].min[k] < merged.min[k] {
906 merged.min[k] = aabbs[i].min[k];
907 }
908 if aabbs[i].max[k] > merged.max[k] {
909 merged.max[k] = aabbs[i].max[k];
910 }
911 }
912 merged
913 }
914 });
915 }
916
917 let mut best_cost = f32::INFINITY;
918 let mut best_split = None;
919
920 for split in 1..num_bins {
921 let left_count: usize = bin_counts[..split].iter().sum();
922 let right_count: usize = bin_counts[split..].iter().sum();
923 if left_count == 0 || right_count == 0 {
924 continue;
925 }
926
927 let left_sa = bin_aabbs[..split]
929 .iter()
930 .flatten()
931 .fold(None::<AabbGpu>, |acc, a| {
932 Some(match acc {
933 None => *a,
934 Some(prev) => {
935 let mut m = prev;
936 for k in 0..3 {
937 if a.min[k] < m.min[k] {
938 m.min[k] = a.min[k];
939 }
940 if a.max[k] > m.max[k] {
941 m.max[k] = a.max[k];
942 }
943 }
944 m
945 }
946 })
947 })
948 .map(|a| aabb_surface_area(&a))
949 .unwrap_or(0.0);
950
951 let right_sa = bin_aabbs[split..]
952 .iter()
953 .flatten()
954 .fold(None::<AabbGpu>, |acc, a| {
955 Some(match acc {
956 None => *a,
957 Some(prev) => {
958 let mut m = prev;
959 for k in 0..3 {
960 if a.min[k] < m.min[k] {
961 m.min[k] = a.min[k];
962 }
963 if a.max[k] > m.max[k] {
964 m.max[k] = a.max[k];
965 }
966 }
967 m
968 }
969 })
970 })
971 .map(|a| aabb_surface_area(&a))
972 .unwrap_or(0.0);
973
974 let cost = left_sa * left_count as f32 + right_sa * right_count as f32;
975 if cost < best_cost {
976 best_cost = cost;
977 best_split = Some(split);
978 }
979 }
980
981 best_split
982}
983
984pub fn build_lbvh(aabbs: &[AabbGpu], cell_size: f32, origin: [f32; 3]) -> Vec<BvhGpuNode> {
997 if aabbs.is_empty() {
998 return Vec::new();
999 }
1000 let sorted = morton_sort(aabbs, cell_size, origin);
1002 build_bvh(&sorted)
1004}
1005
1006#[cfg(test)]
1011mod tests {
1012 use super::*;
1013
1014 #[test]
1015 fn aabb_overlap_detects_overlapping_boxes() {
1016 #[rustfmt::skip]
1018 let aabbs: Vec<f64> = vec![
1019 0.0, 2.0, 0.0, 2.0, 0.0, 2.0, 1.0, 3.0, 1.0, 3.0, 1.0, 3.0, ];
1022 let mut outputs = vec![Vec::new()];
1023 AabbOverlapKernel.execute(&[&aabbs], &mut outputs, 2);
1024 assert_eq!(outputs[0], vec![0.0, 1.0]);
1025 }
1026
1027 #[test]
1028 fn aabb_overlap_rejects_non_overlapping_boxes() {
1029 #[rustfmt::skip]
1030 let aabbs: Vec<f64> = vec![
1031 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 5.0, 6.0, 5.0, 6.0, 5.0, 6.0, ];
1034 let mut outputs = vec![Vec::new()];
1035 AabbOverlapKernel.execute(&[&aabbs], &mut outputs, 2);
1036 assert!(outputs[0].is_empty());
1037 }
1038
1039 #[test]
1040 fn test_broadphase_gpu_matches_cpu() {
1041 #[rustfmt::skip]
1043 let aabbs: Vec<f64> = vec![
1044 0.0, 1.5, 0.0, 1.5, 0.0, 1.5, 1.0, 2.5, 0.0, 1.5, 0.0, 1.5, 3.0, 4.0, 0.0, 1.5, 0.0, 1.5, ];
1048
1049 let mut gpu_outputs = vec![Vec::new()];
1051 AabbOverlapKernel.execute(&[&aabbs], &mut gpu_outputs, 3);
1052
1053 let n = aabbs.len() / 6;
1055 let mut cpu_pairs: Vec<(usize, usize)> = Vec::new();
1056 for i in 0..n {
1057 for j in (i + 1)..n {
1058 let a = &aabbs[i * 6..(i + 1) * 6];
1059 let b = &aabbs[j * 6..(j + 1) * 6];
1060 let overlaps = a[0] <= b[1]
1061 && a[1] >= b[0]
1062 && a[2] <= b[3]
1063 && a[3] >= b[2]
1064 && a[4] <= b[5]
1065 && a[5] >= b[4];
1066 if overlaps {
1067 cpu_pairs.push((i, j));
1068 }
1069 }
1070 }
1071
1072 let raw = &gpu_outputs[0];
1074 assert_eq!(raw.len() % 2, 0, "GPU output length must be even");
1075 let gpu_pairs: Vec<(usize, usize)> = raw
1076 .chunks(2)
1077 .map(|c| (c[0] as usize, c[1] as usize))
1078 .collect();
1079
1080 assert_eq!(
1081 gpu_pairs, cpu_pairs,
1082 "GPU broadphase pairs do not match CPU brute-force pairs"
1083 );
1084 }
1085
1086 #[test]
1088 fn test_morton_code_correctness() {
1089 assert_eq!(morton_code(0, 0, 0), 0);
1090 assert_eq!(morton_code(1, 0, 0), 1);
1092 assert_eq!(morton_code(0, 1, 0), 2);
1094 assert_eq!(morton_code(0, 0, 1), 4);
1096 assert_eq!(morton_code(1, 1, 1), 7);
1098 }
1099
1100 #[test]
1102 fn test_sap_finds_overlap() {
1103 let a = AabbGpu::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0], 0);
1104 let b = AabbGpu::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0], 1);
1105 let c = AabbGpu::new([10.0, 10.0, 10.0], [12.0, 12.0, 12.0], 2);
1106
1107 let pairs = SortAndSweepGpu::detect_pairs(&[a, b, c]);
1108 assert!(
1109 pairs.contains(&(0, 1)),
1110 "SAP should find pair (0,1), got {pairs:?}"
1111 );
1112 assert!(!pairs.contains(&(0, 2)), "SAP should not find pair (0,2)");
1113 assert!(!pairs.contains(&(1, 2)), "SAP should not find pair (1,2)");
1114 }
1115
1116 #[test]
1118 fn test_sap_no_overlap() {
1119 let a = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0);
1120 let b = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0], 1);
1121 let pairs = SortAndSweepGpu::detect_pairs(&[a, b]);
1122 assert!(
1123 pairs.is_empty(),
1124 "Should be no overlapping pairs, got {pairs:?}"
1125 );
1126 }
1127
1128 #[test]
1130 fn test_uniform_grid_pair_detection() {
1131 let a = AabbGpu::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0], 0);
1132 let b = AabbGpu::new([1.5, 1.5, 1.5], [3.5, 3.5, 3.5], 1);
1133 let c = AabbGpu::new([10.0, 10.0, 10.0], [12.0, 12.0, 12.0], 2);
1134
1135 let grid = UniformGridGpu::new(5.0, [0.0, 0.0, 0.0], [10, 10, 10]);
1136 let pairs = grid.query_pairs(&[a, b, c]);
1137
1138 assert!(
1140 pairs.contains(&(0, 1)),
1141 "Grid should find pair (0,1), got {pairs:?}"
1142 );
1143 assert!(
1144 !pairs
1145 .iter()
1146 .any(|&(x, y)| x == 0 && y == 2 || x == 2 && y == 0)
1147 );
1148 }
1149
1150 #[test]
1152 fn test_bvh_depth() {
1153 let aabbs: Vec<AabbGpu> = (0..8)
1154 .map(|i| {
1155 let x = (i * 3) as f32;
1156 AabbGpu::new([x, 0.0, 0.0], [x + 1.0, 1.0, 1.0], i)
1157 })
1158 .collect();
1159
1160 let nodes = build_bvh(&aabbs);
1161
1162 assert!(!nodes.is_empty(), "BVH should have at least one node");
1164 assert!(
1165 nodes.len() <= 2 * aabbs.len(),
1166 "BVH node count unexpected: {}",
1167 nodes.len()
1168 );
1169
1170 let root = &nodes[0];
1172 assert!(root.aabb.min[0] <= 0.0 + 1e-5, "Root min_x too large");
1173 assert!(
1174 root.aabb.max[0] >= 22.0 - 1e-5,
1175 "Root max_x too small: {}",
1176 root.aabb.max[0]
1177 );
1178 }
1179
1180 #[test]
1182 fn test_aabb_gpu_overlap_symmetric() {
1183 let a = AabbGpu::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0], 0);
1184 let b = AabbGpu::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0], 1);
1185 assert_eq!(a.overlaps(&b), b.overlaps(&a));
1186
1187 let c = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0], 2);
1188 assert_eq!(a.overlaps(&c), c.overlaps(&a));
1189 assert!(!a.overlaps(&c));
1190 }
1191
1192 #[test]
1195 fn test_sort_and_sweep_flat_finds_pair() {
1196 #[rustfmt::skip]
1197 let aabbs: Vec<f64> = vec![
1198 0.0, 2.0, 0.0, 2.0, 0.0, 2.0, 1.0, 3.0, 1.0, 3.0, 1.0, 3.0, 5.0, 6.0, 5.0, 6.0, 5.0, 6.0, ];
1202 let pairs = sort_and_sweep_flat(&aabbs);
1203 assert!(pairs.contains(&(0, 1)), "should find (0,1)");
1204 assert!(!pairs.iter().any(|&(a, b)| (a == 0 || a == 1) && b == 2));
1205 }
1206
1207 #[test]
1208 fn test_sort_and_sweep_flat_empty() {
1209 let pairs = sort_and_sweep_flat(&[]);
1210 assert!(pairs.is_empty());
1211 }
1212
1213 #[test]
1214 fn test_sort_and_sweep_flat_no_overlap() {
1215 #[rustfmt::skip]
1216 let aabbs: Vec<f64> = vec![
1217 0.0, 1.0, 0.0, 1.0, 0.0, 1.0,
1218 2.0, 3.0, 2.0, 3.0, 2.0, 3.0,
1219 ];
1220 let pairs = sort_and_sweep_flat(&aabbs);
1221 assert!(pairs.is_empty());
1222 }
1223
1224 #[test]
1225 fn test_assign_to_grid_cells() {
1226 #[rustfmt::skip]
1227 let aabbs: Vec<f64> = vec![
1228 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, ];
1230 let cells = assign_to_grid_cells(&aabbs, 2.0, [0.0, 0.0, 0.0]);
1231 assert!(!cells.is_empty());
1232 assert!(cells.iter().all(|&(_, idx)| idx == 0));
1234 }
1235
1236 #[test]
1237 fn test_pairs_from_grid_assignments() {
1238 let assignments = vec![(0u64, 0usize), (0u64, 1usize)];
1240 let pairs = pairs_from_grid_assignments(&assignments);
1241 assert!(pairs.contains(&(0, 1)));
1242 }
1243
1244 #[test]
1245 fn test_pairs_from_grid_assignments_no_dup() {
1246 let assignments = vec![
1248 (0u64, 0usize),
1249 (0u64, 1usize),
1250 (1u64, 0usize),
1251 (1u64, 1usize),
1252 ];
1253 let pairs = pairs_from_grid_assignments(&assignments);
1254 let count = pairs.iter().filter(|&&p| p == (0, 1)).count();
1256 assert_eq!(count, 1);
1257 }
1258
1259 #[test]
1260 fn test_morton_sort_orders_aabbs() {
1261 let aabbs: Vec<AabbGpu> = vec![
1262 AabbGpu::new([4.0, 0.0, 0.0], [5.0, 1.0, 1.0], 0),
1263 AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 1),
1264 AabbGpu::new([2.0, 2.0, 2.0], [3.0, 3.0, 3.0], 2),
1265 ];
1266 let sorted = morton_sort(&aabbs, 1.0, [0.0, 0.0, 0.0]);
1267 assert_eq!(
1269 sorted[0].body_id, 1,
1270 "body at origin should be first in Morton order"
1271 );
1272 }
1273
1274 #[test]
1275 fn test_morton_key_reproducible() {
1276 let a = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0);
1277 let k1 = morton_key_for_aabb(&a, 1.0, [0.0, 0.0, 0.0]);
1278 let k2 = morton_key_for_aabb(&a, 1.0, [0.0, 0.0, 0.0]);
1279 assert_eq!(k1, k2);
1280 }
1281
1282 #[test]
1283 fn test_compact_pair_list_insert_dedup() {
1284 let mut list = CompactPairList::new();
1285 list.insert(0, 1);
1286 list.insert(1, 0); list.insert(0, 1); assert_eq!(list.len(), 1);
1289 }
1290
1291 #[test]
1292 fn test_compact_pair_list_contains() {
1293 let mut list = CompactPairList::new();
1294 list.insert(2, 5);
1295 assert!(list.contains(2, 5));
1296 assert!(list.contains(5, 2));
1297 assert!(!list.contains(0, 1));
1298 }
1299
1300 #[test]
1301 fn test_compact_pair_list_remove_body() {
1302 let mut list = CompactPairList::new();
1303 list.insert(0, 1);
1304 list.insert(0, 2);
1305 list.insert(1, 2);
1306 list.remove_body(0);
1307 assert!(!list.contains(0, 1));
1308 assert!(!list.contains(0, 2));
1309 assert!(list.contains(1, 2));
1310 }
1311
1312 #[test]
1313 fn test_compact_pair_list_insert_all() {
1314 let mut list = CompactPairList::new();
1315 list.insert_all(&[(0, 1), (1, 2), (0, 2)]);
1316 assert_eq!(list.len(), 3);
1317 }
1318
1319 #[test]
1320 fn test_compact_pair_list_sort() {
1321 let mut list = CompactPairList::new();
1322 list.insert(3, 4);
1323 list.insert(0, 1);
1324 list.insert(1, 2);
1325 list.sort();
1326 assert_eq!(list.pairs()[0], (0, 1));
1327 }
1328
1329 #[test]
1332 fn test_lbvh_query_pairs_finds_overlap() {
1333 let a = AabbGpu::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0], 0);
1335 let b = AabbGpu::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0], 1);
1336 let nodes = build_bvh(&[a, b]);
1337 let pairs = lbvh_query_pairs(&nodes);
1338 assert!(
1339 pairs.contains(&(0, 1)),
1340 "LBVH traversal should find (0,1): {pairs:?}"
1341 );
1342 }
1343
1344 #[test]
1345 fn test_lbvh_query_pairs_no_overlap() {
1346 let a = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0);
1347 let b = AabbGpu::new([5.0, 5.0, 5.0], [6.0, 6.0, 6.0], 1);
1348 let nodes = build_bvh(&[a, b]);
1349 let pairs = lbvh_query_pairs(&nodes);
1350 assert!(
1351 pairs.is_empty(),
1352 "Non-overlapping: should have no pairs: {pairs:?}"
1353 );
1354 }
1355
1356 #[test]
1357 fn test_lbvh_query_empty_bvh() {
1358 let pairs = lbvh_query_pairs(&[]);
1359 assert!(pairs.is_empty());
1360 }
1361
1362 #[test]
1365 fn test_refit_bvh_no_panic() {
1366 let aabbs: Vec<AabbGpu> = (0..4)
1367 .map(|i| {
1368 let x = (i * 2) as f32;
1369 AabbGpu::new([x, 0.0, 0.0], [x + 1.0, 1.0, 1.0], i)
1370 })
1371 .collect();
1372 let mut nodes = build_bvh(&aabbs);
1373 for node in nodes.iter_mut() {
1375 if node.is_leaf() {
1376 node.aabb.max[0] += 0.5;
1377 }
1378 }
1379 refit_bvh(&mut nodes);
1380 assert!(!nodes.is_empty());
1382 }
1383
1384 #[test]
1385 fn test_refit_bvh_root_encompasses_leaves() {
1386 let aabbs: Vec<AabbGpu> = vec![
1387 AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0),
1388 AabbGpu::new([10.0, 0.0, 0.0], [11.0, 1.0, 1.0], 1),
1389 ];
1390 let mut nodes = build_bvh(&aabbs);
1391 refit_bvh(&mut nodes);
1392 assert!(nodes[0].aabb.min[0] <= 0.0 + 1e-5);
1394 assert!(nodes[0].aabb.max[0] >= 11.0 - 1e-5);
1395 }
1396
1397 #[test]
1400 fn test_aabb_surface_area() {
1401 let a = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 2.0, 3.0], 0);
1402 let sa = aabb_surface_area(&a);
1403 assert!((sa - 22.0).abs() < 1e-5, "SA = {sa}");
1405 }
1406
1407 #[test]
1408 fn test_aabb_surface_area_unit_cube() {
1409 let a = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0);
1410 let sa = aabb_surface_area(&a);
1411 assert!((sa - 6.0).abs() < 1e-5, "unit cube SA = {sa}");
1412 }
1413
1414 #[test]
1415 fn test_bvh_sah_cost_positive() {
1416 let aabbs: Vec<AabbGpu> = (0..4)
1417 .map(|i| {
1418 let x = (i * 3) as f32;
1419 AabbGpu::new([x, 0.0, 0.0], [x + 2.0, 2.0, 2.0], i)
1420 })
1421 .collect();
1422 let nodes = build_bvh(&aabbs);
1423 let cost = bvh_sah_cost(&nodes, 1.0, 1.0);
1424 assert!(cost > 0.0, "SAH cost should be positive: {cost}");
1425 assert!(cost.is_finite(), "SAH cost should be finite");
1426 }
1427
1428 #[test]
1429 fn test_bvh_depth_single_leaf() {
1430 let aabbs = vec![AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0)];
1431 let nodes = build_bvh(&aabbs);
1432 let d = bvh_depth(&nodes);
1433 assert_eq!(d, 0, "single leaf depth = {d}");
1434 }
1435
1436 #[test]
1437 fn test_bvh_depth_two_leaves() {
1438 let aabbs = vec![
1439 AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0),
1440 AabbGpu::new([2.0, 0.0, 0.0], [3.0, 1.0, 1.0], 1),
1441 ];
1442 let nodes = build_bvh(&aabbs);
1443 let d = bvh_depth(&nodes);
1444 assert!(d >= 1, "two-leaf BVH depth >= 1, got {d}");
1445 }
1446
1447 #[test]
1448 fn test_bvh_leaf_count() {
1449 let aabbs: Vec<AabbGpu> = (0..8)
1450 .map(|i| {
1451 let x = (i * 2) as f32;
1452 AabbGpu::new([x, 0.0, 0.0], [x + 1.0, 1.0, 1.0], i)
1453 })
1454 .collect();
1455 let nodes = build_bvh(&aabbs);
1456 let leaves = bvh_leaf_count(&nodes);
1457 assert_eq!(leaves, 8, "Expected 8 leaves, got {leaves}");
1458 }
1459
1460 #[test]
1463 fn test_sap_incremental_removes_moved() {
1464 let a = AabbGpu::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0], 0);
1465 let b = AabbGpu::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0], 1);
1466 let c = AabbGpu::new([10.0, 10.0, 10.0], [11.0, 11.0, 11.0], 2);
1467 let initial_pairs = SortAndSweepGpu::detect_pairs(&[a, b, c]);
1468 let mut existing = CompactPairList::new();
1469 existing.insert_all(&initial_pairs);
1470
1471 let a_moved = AabbGpu::new([20.0, 20.0, 20.0], [21.0, 21.0, 21.0], 0);
1473 let updated = sap_incremental_update(&existing, &[a_moved, b, c], &[0]);
1474
1475 assert!(
1477 !updated.contains(0, 1),
1478 "pair (0,1) should be removed after move"
1479 );
1480 }
1481
1482 #[test]
1483 fn test_sap_incremental_adds_new_overlap() {
1484 let _a = AabbGpu::new([10.0, 0.0, 0.0], [11.0, 1.0, 1.0], 0);
1485 let b = AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 1);
1486 let existing = CompactPairList::new(); let a_moved = AabbGpu::new([0.5, 0.5, 0.5], [1.5, 1.5, 1.5], 0);
1490 let updated = sap_incremental_update(&existing, &[a_moved, b], &[0]);
1491 assert!(
1492 updated.contains(0, 1),
1493 "pair (0,1) should be added after move: {:?}",
1494 updated.pairs()
1495 );
1496 }
1497
1498 #[test]
1501 fn test_sah_best_split_basic() {
1502 let aabbs: Vec<AabbGpu> = (0..8)
1503 .map(|i| {
1504 let x = (i * 2) as f32;
1505 AabbGpu::new([x, 0.0, 0.0], [x + 1.5, 1.0, 1.0], i)
1506 })
1507 .collect();
1508 let indices: Vec<usize> = (0..8).collect();
1509 let result = sah_best_split(&aabbs, &indices, 0, 8);
1510 assert!(result.is_some(), "SAH split should find a valid split");
1511 }
1512
1513 #[test]
1514 fn test_sah_best_split_single_element() {
1515 let aabbs = vec![AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0)];
1516 let result = sah_best_split(&aabbs, &[0], 0, 4);
1517 assert!(result.is_none(), "Single element: no split possible");
1518 }
1519
1520 #[test]
1523 fn test_build_lbvh_nonempty() {
1524 let aabbs: Vec<AabbGpu> = (0..6)
1525 .map(|i| {
1526 let x = (i * 2) as f32;
1527 AabbGpu::new([x, 0.0, 0.0], [x + 1.0, 1.0, 1.0], i)
1528 })
1529 .collect();
1530 let nodes = build_lbvh(&aabbs, 1.0, [0.0, 0.0, 0.0]);
1531 assert!(!nodes.is_empty(), "LBVH should build non-empty node list");
1532 assert!(nodes.len() <= 2 * aabbs.len());
1533 }
1534
1535 #[test]
1536 fn test_build_lbvh_empty() {
1537 let nodes = build_lbvh(&[], 1.0, [0.0, 0.0, 0.0]);
1538 assert!(nodes.is_empty());
1539 }
1540
1541 #[test]
1542 fn test_build_lbvh_single() {
1543 let aabbs = vec![AabbGpu::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0)];
1544 let nodes = build_lbvh(&aabbs, 1.0, [0.0, 0.0, 0.0]);
1545 assert_eq!(nodes.len(), 1);
1546 assert!(nodes[0].is_leaf());
1547 }
1548
1549 #[test]
1550 fn test_lbvh_vs_brute_force_pairs() {
1551 let aabbs: Vec<AabbGpu> = (0..6)
1553 .map(|i| {
1554 let x = (i as f32) * 0.8;
1555 AabbGpu::new([x, 0.0, 0.0], [x + 1.0, 1.0, 1.0], i as u32)
1556 })
1557 .collect();
1558
1559 let lbvh_nodes = build_lbvh(&aabbs, 0.5, [0.0, 0.0, 0.0]);
1560 let mut lbvh_pairs = lbvh_query_pairs(&lbvh_nodes);
1561 lbvh_pairs.sort();
1562 lbvh_pairs.dedup();
1563
1564 let mut brute: Vec<(u32, u32)> = Vec::new();
1565 for i in 0..aabbs.len() {
1566 for j in (i + 1)..aabbs.len() {
1567 if aabbs[i].overlaps(&aabbs[j]) {
1568 brute.push((aabbs[i].body_id, aabbs[j].body_id));
1569 }
1570 }
1571 }
1572 brute.sort();
1573
1574 assert_eq!(
1575 lbvh_pairs, brute,
1576 "LBVH pairs {lbvh_pairs:?} != brute force {brute:?}"
1577 );
1578 }
1579}