1use bytemuck::{Pod, Zeroable};
23
24use super::distance::Aabb;
25use super::spatial_order::sort_by_morton_3d;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum BvhError {
34 NodeBufferTooSmall { required: usize },
36 IndexBufferTooSmall { required: usize },
38 CodeBufferTooSmall { required: usize },
40 SortBufferTooSmall { required: usize },
42 InvalidAabb { index: usize },
44}
45
46#[repr(C)]
62#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
63pub struct BvhNode {
64 pub bbox_min: [f32; 3],
65 pub bbox_max: [f32; 3],
66 pub left_or_first: u32,
67 pub right_or_count: u32,
68 pub parent: u32,
69 pub node_type: u32,
70}
71
72impl Default for BvhNode {
73 fn default() -> Self {
74 Self {
75 bbox_min: [0.0; 3],
76 bbox_max: [0.0; 3],
77 left_or_first: 0,
78 right_or_count: 0,
79 parent: super::topology::INVALID_INDEX,
80 node_type: 0,
81 }
82 }
83}
84
85pub const BVH_NODE_SIZE: usize = 40;
87
88pub const MAX_BVH_DEPTH: usize = 32;
90
91const LEAF_SIZE: usize = 4;
93
94impl From<super::spatial_order::SpatialOrderError> for BvhError {
95 fn from(err: super::spatial_order::SpatialOrderError) -> Self {
96 match err {
97 super::spatial_order::SpatialOrderError::CodeBufferTooSmall { required } => {
98 BvhError::CodeBufferTooSmall { required }
99 }
100 super::spatial_order::SpatialOrderError::IndexBufferTooSmall { required } => {
101 BvhError::SortBufferTooSmall { required }
102 }
103 super::spatial_order::SpatialOrderError::NonFiniteCoordinate { index } => {
104 BvhError::InvalidAabb { index }
105 }
106 }
107 }
108}
109
110fn build_recursive(
114 primitives: &[Aabb],
115 prim_indices: &mut [u32],
116 start: usize,
117 count: usize,
118 nodes: &mut [BvhNode],
119 next_node: &mut usize,
120 parent: u32,
121) -> usize {
122 let node_idx = *next_node;
123 *next_node += 1;
124
125 let mut bbox = Aabb::new(
127 primitives[prim_indices[start] as usize].min,
128 primitives[prim_indices[start] as usize].max,
129 );
130 for i in 1..count {
131 let p_idx = prim_indices[start + i] as usize;
132 bbox = bbox.union(&primitives[p_idx]);
133 }
134
135 if count <= LEAF_SIZE {
136 nodes[node_idx] = BvhNode {
137 bbox_min: [bbox.min.x as f32, bbox.min.y as f32, bbox.min.z as f32],
138 bbox_max: [bbox.max.x as f32, bbox.max.y as f32, bbox.max.z as f32],
139 left_or_first: start as u32,
140 right_or_count: count as u32,
141 parent,
142 node_type: 1,
143 };
144 return 1;
145 }
146
147 let mid = start + count / 2;
149
150 let left_child = *next_node;
152 let left_size = build_recursive(
153 primitives,
154 prim_indices,
155 start,
156 count / 2,
157 nodes,
158 next_node,
159 node_idx as u32,
160 );
161
162 let right_child = *next_node;
164 let right_size = build_recursive(
165 primitives,
166 prim_indices,
167 mid,
168 count - count / 2,
169 nodes,
170 next_node,
171 node_idx as u32,
172 );
173
174 nodes[node_idx] = BvhNode {
175 bbox_min: [bbox.min.x as f32, bbox.min.y as f32, bbox.min.z as f32],
176 bbox_max: [bbox.max.x as f32, bbox.max.y as f32, bbox.max.z as f32],
177 left_or_first: left_child as u32,
178 right_or_count: right_child as u32,
179 parent,
180 node_type: 0,
181 };
182
183 1 + left_size + right_size
184}
185
186pub fn build_bvh_recursive(
196 primitives: &[Aabb],
197 nodes: &mut [BvhNode],
198 prim_indices: &mut [u32],
199 morton_codes: &mut [u64],
200 sort_indices: &mut [u32],
201) -> Result<(usize, usize), BvhError> {
202 let n = primitives.len();
203 if n == 0 {
204 return Ok((0, 0));
205 }
206
207 for (i, aabb) in primitives.iter().enumerate() {
209 if aabb.min.x > aabb.max.x || aabb.min.y > aabb.max.y || aabb.min.z > aabb.max.z {
210 return Err(BvhError::InvalidAabb { index: i });
211 }
212 }
213
214 if morton_codes.len() < n {
215 return Err(BvhError::CodeBufferTooSmall { required: n });
216 }
217 if sort_indices.len() < n {
218 return Err(BvhError::SortBufferTooSmall { required: n });
219 }
220 if prim_indices.len() < n {
221 return Err(BvhError::IndexBufferTooSmall { required: n });
222 }
223
224 let max_nodes = 2 * n - 1;
225 if nodes.len() < max_nodes {
226 return Err(BvhError::NodeBufferTooSmall {
227 required: max_nodes,
228 });
229 }
230
231 let centroids: Vec<[f64; 3]> = primitives
233 .iter()
234 .map(|aabb| {
235 [
236 0.5 * (aabb.min.x + aabb.max.x) as f64,
237 0.5 * (aabb.min.y + aabb.max.y) as f64,
238 0.5 * (aabb.min.z + aabb.max.z) as f64,
239 ]
240 })
241 .collect();
242
243 sort_by_morton_3d(¢roids, morton_codes, sort_indices)?;
245 prim_indices[..n].copy_from_slice(&sort_indices[..n]);
246
247 let mut next_node = 0usize;
249 let node_count = build_recursive(
250 primitives,
251 prim_indices,
252 0,
253 n,
254 nodes,
255 &mut next_node,
256 super::topology::INVALID_INDEX,
257 );
258
259 Ok((node_count, 0))
260}
261
262pub fn query_overlap(
273 nodes: &[BvhNode],
274 primitives: &[Aabb],
275 prim_indices: &[u32],
276 root: usize,
277 node_count: usize,
278 query_bbox: &Aabb,
279 out_indices: &mut [u32],
280 stack: &mut [u32],
281) -> Result<usize, BvhError> {
282 if node_count == 0 {
283 return Ok(0);
284 }
285 if stack.len() < MAX_BVH_DEPTH * 2 {
286 return Err(BvhError::SortBufferTooSmall {
287 required: MAX_BVH_DEPTH * 2,
288 });
289 }
290
291 let mut out_count = 0;
292 let mut stack_top = 0usize;
293 stack[stack_top] = root as u32;
294 stack_top += 1;
295
296 while stack_top > 0 {
297 stack_top -= 1;
298 let node_idx = stack[stack_top] as usize;
299
300 let node = &nodes[node_idx];
301 let node_bbox = Aabb::new(
302 crate::specialized_libs::computational_geometry::Point3::new(
303 node.bbox_min[0] as f64,
304 node.bbox_min[1] as f64,
305 node.bbox_min[2] as f64,
306 ),
307 crate::specialized_libs::computational_geometry::Point3::new(
308 node.bbox_max[0] as f64,
309 node.bbox_max[1] as f64,
310 node.bbox_max[2] as f64,
311 ),
312 );
313
314 if !node_bbox.overlaps(query_bbox) {
315 continue;
316 }
317
318 if node.node_type == 1 {
320 let start = node.left_or_first as usize;
322 let count = node.right_or_count as usize;
323 for i in 0..count {
324 if out_count >= out_indices.len() {
325 return Ok(out_count);
326 }
327 let p_idx = prim_indices[start + i] as usize;
328 if primitives[p_idx].overlaps(query_bbox) {
329 out_indices[out_count] = prim_indices[start + i];
330 out_count += 1;
331 }
332 }
333 } else {
334 if stack_top + 2 > stack.len() {
336 break;
337 }
338 stack[stack_top] = node.right_or_count; stack_top += 1;
340 stack[stack_top] = node.left_or_first; stack_top += 1;
342 }
343 }
344
345 Ok(out_count)
346}
347
348pub fn query_closest(
355 nodes: &[BvhNode],
356 primitives: &[Aabb],
357 prim_indices: &[u32],
358 root: usize,
359 node_count: usize,
360 point: super::primitives::Point3,
361 stack: &mut [u32],
362) -> Result<Option<(u32, f64)>, BvhError> {
363 if node_count == 0 {
364 return Ok(None);
365 }
366 if stack.len() < MAX_BVH_DEPTH * 2 {
367 return Err(BvhError::SortBufferTooSmall {
368 required: MAX_BVH_DEPTH * 2,
369 });
370 }
371
372 let mut best_idx: Option<u32> = None;
373 let mut best_dist_sq = f64::INFINITY;
374
375 let mut stack_top = 0usize;
376 stack[stack_top] = root as u32;
377 stack_top += 1;
378
379 while stack_top > 0 {
380 stack_top -= 1;
381 let node_idx = stack[stack_top] as usize;
382 let node = &nodes[node_idx];
383
384 let node_bbox = Aabb::new(
385 super::primitives::Point3::new(
386 node.bbox_min[0] as f64,
387 node.bbox_min[1] as f64,
388 node.bbox_min[2] as f64,
389 ),
390 super::primitives::Point3::new(
391 node.bbox_max[0] as f64,
392 node.bbox_max[1] as f64,
393 node.bbox_max[2] as f64,
394 ),
395 );
396
397 let node_dist_sq = node_bbox.distance_sq_to_point(point);
399 if node_dist_sq > best_dist_sq {
400 continue;
401 }
402
403 if node.node_type == 1 {
404 let start = node.left_or_first as usize;
406 let count = node.right_or_count as usize;
407 for i in 0..count {
408 let p_idx = prim_indices[start + i] as usize;
409 let dist_sq = primitives[p_idx].distance_sq_to_point(point);
410 if dist_sq < best_dist_sq {
411 best_dist_sq = dist_sq;
412 best_idx = Some(prim_indices[start + i]);
413 }
414 }
415 } else {
416 if stack_top + 2 > stack.len() {
418 break;
419 }
420 stack[stack_top] = node.right_or_count;
421 stack_top += 1;
422 stack[stack_top] = node.left_or_first;
423 stack_top += 1;
424 }
425 }
426
427 Ok(best_idx.map(|idx| (idx, best_dist_sq)))
428}
429
430#[cfg(test)]
435mod tests {
436 use super::super::primitives::Point3;
437 use super::*;
438
439 fn make_aabb(min: [f64; 3], max: [f64; 3]) -> Aabb {
440 Aabb::new(
441 Point3::new(min[0], min[1], min[2]),
442 Point3::new(max[0], max[1], max[2]),
443 )
444 }
445
446 fn unit_cubes() -> Vec<Aabb> {
447 let mut prims = Vec::new();
449 for x in 0..2 {
450 for y in 0..2 {
451 for z in 0..2 {
452 let min = [x as f64, y as f64, z as f64];
453 let max = [(x + 1) as f64, (y + 1) as f64, (z + 1) as f64];
454 prims.push(make_aabb(min, max));
455 }
456 }
457 }
458 prims
459 }
460
461 #[test]
462 fn bvh_node_is_pod_with_exact_size() {
463 assert_eq!(std::mem::size_of::<BvhNode>(), BVH_NODE_SIZE);
464 assert_eq!(std::mem::align_of::<BvhNode>(), 4);
465 }
466
467 #[test]
468 fn build_bvh_8_cubes() {
469 let prims = unit_cubes();
470 let n = prims.len();
471 let mut nodes = vec![BvhNode::default(); 2 * n];
472 let mut prim_indices = vec![0u32; n];
473 let mut morton_codes = vec![0u64; n];
474 let mut sort_indices = vec![0u32; n];
475
476 let (node_count, root) = build_bvh_recursive(
477 &prims,
478 &mut nodes,
479 &mut prim_indices,
480 &mut morton_codes,
481 &mut sort_indices,
482 )
483 .unwrap();
484
485 assert!(node_count > 0);
486 assert_eq!(root, 0);
487 assert_eq!(node_count, 3);
489 }
490
491 #[test]
492 fn build_bvh_deterministic() {
493 let prims = unit_cubes();
494 let n = prims.len();
495
496 let mut nodes_a = vec![BvhNode::default(); 2 * n];
497 let mut indices_a = vec![0u32; n];
498 let mut codes_a = vec![0u64; n];
499 let mut sort_a = vec![0u32; n];
500 let (count_a, _) = build_bvh_recursive(
501 &prims,
502 &mut nodes_a,
503 &mut indices_a,
504 &mut codes_a,
505 &mut sort_a,
506 )
507 .unwrap();
508
509 let mut nodes_b = vec![BvhNode::default(); 2 * n];
510 let mut indices_b = vec![0u32; n];
511 let mut codes_b = vec![0u64; n];
512 let mut sort_b = vec![0u32; n];
513 let (count_b, _) = build_bvh_recursive(
514 &prims,
515 &mut nodes_b,
516 &mut indices_b,
517 &mut codes_b,
518 &mut sort_b,
519 )
520 .unwrap();
521
522 assert_eq!(count_a, count_b);
523 assert_eq!(nodes_a[..count_a], nodes_b[..count_b]);
524 assert_eq!(indices_a, indices_b);
525 }
526
527 #[test]
528 fn query_overlap_finds_correct_primitives() {
529 let prims = unit_cubes();
530 let n = prims.len();
531 let mut nodes = vec![BvhNode::default(); 2 * n];
532 let mut prim_indices = vec![0u32; n];
533 let mut morton_codes = vec![0u64; n];
534 let mut sort_indices = vec![0u32; n];
535
536 let (node_count, root) = build_bvh_recursive(
537 &prims,
538 &mut nodes,
539 &mut prim_indices,
540 &mut morton_codes,
541 &mut sort_indices,
542 )
543 .unwrap();
544
545 let query = make_aabb([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]);
547 let mut out = vec![0u32; n];
548 let mut stack = vec![0u32; MAX_BVH_DEPTH * 2];
549 let count = query_overlap(
550 &nodes,
551 &prims,
552 &prim_indices,
553 root,
554 node_count,
555 &query,
556 &mut out,
557 &mut stack,
558 )
559 .unwrap();
560
561 assert_eq!(count, 1);
562 let found_prim = prims[out[0] as usize];
564 assert_eq!(found_prim.min, Point3::new(0.0, 0.0, 0.0));
565 }
566
567 #[test]
568 fn query_overlap_all() {
569 let prims = unit_cubes();
570 let n = prims.len();
571 let mut nodes = vec![BvhNode::default(); 2 * n];
572 let mut prim_indices = vec![0u32; n];
573 let mut morton_codes = vec![0u64; n];
574 let mut sort_indices = vec![0u32; n];
575
576 let (node_count, root) = build_bvh_recursive(
577 &prims,
578 &mut nodes,
579 &mut prim_indices,
580 &mut morton_codes,
581 &mut sort_indices,
582 )
583 .unwrap();
584
585 let query = make_aabb([-10.0, -10.0, -10.0], [10.0, 10.0, 10.0]);
587 let mut out = vec![0u32; n];
588 let mut stack = vec![0u32; MAX_BVH_DEPTH * 2];
589 let count = query_overlap(
590 &nodes,
591 &prims,
592 &prim_indices,
593 root,
594 node_count,
595 &query,
596 &mut out,
597 &mut stack,
598 )
599 .unwrap();
600
601 assert_eq!(count, n);
602 }
603
604 #[test]
605 fn query_closest_finds_nearest() {
606 let prims = unit_cubes();
607 let n = prims.len();
608 let mut nodes = vec![BvhNode::default(); 2 * n];
609 let mut prim_indices = vec![0u32; n];
610 let mut morton_codes = vec![0u64; n];
611 let mut sort_indices = vec![0u32; n];
612
613 let (node_count, root) = build_bvh_recursive(
614 &prims,
615 &mut nodes,
616 &mut prim_indices,
617 &mut morton_codes,
618 &mut sort_indices,
619 )
620 .unwrap();
621
622 let point = Point3::new(0.1, 0.1, 0.1);
623 let mut stack = vec![0u32; MAX_BVH_DEPTH * 2];
624 let result = query_closest(
625 &nodes,
626 &prims,
627 &prim_indices,
628 root,
629 node_count,
630 point,
631 &mut stack,
632 )
633 .unwrap();
634
635 assert!(result.is_some());
636 let (idx, dist_sq) = result.unwrap();
637 let prim = &prims[idx as usize];
639 assert_eq!(prim.min, Point3::new(0.0, 0.0, 0.0));
640 assert!(dist_sq < 1e-12);
642 }
643
644 #[test]
645 fn query_closest_outside() {
646 let prims = unit_cubes();
647 let n = prims.len();
648 let mut nodes = vec![BvhNode::default(); 2 * n];
649 let mut prim_indices = vec![0u32; n];
650 let mut morton_codes = vec![0u64; n];
651 let mut sort_indices = vec![0u32; n];
652
653 let (node_count, root) = build_bvh_recursive(
654 &prims,
655 &mut nodes,
656 &mut prim_indices,
657 &mut morton_codes,
658 &mut sort_indices,
659 )
660 .unwrap();
661
662 let point = Point3::new(5.0, 5.0, 5.0);
664 let mut stack = vec![0u32; MAX_BVH_DEPTH * 2];
665 let result = query_closest(
666 &nodes,
667 &prims,
668 &prim_indices,
669 root,
670 node_count,
671 point,
672 &mut stack,
673 )
674 .unwrap();
675
676 assert!(result.is_some());
677 let (_idx, dist_sq) = result.unwrap();
678 assert!((dist_sq - 27.0).abs() < 1e-10, "dist_sq={dist_sq}");
680 }
681
682 #[test]
683 fn differential_vs_brute_force_overlap() {
684 let prims: Vec<Aabb> = (0..20)
686 .map(|i| {
687 let x = (i % 5) as f64;
688 let y = ((i / 5) % 4) as f64;
689 let z = (i / 20) as f64;
690 make_aabb([x, y, z], [x + 0.8, y + 0.8, z + 0.8])
691 })
692 .collect();
693 let n = prims.len();
694 let mut nodes = vec![BvhNode::default(); 2 * n];
695 let mut prim_indices = vec![0u32; n];
696 let mut morton_codes = vec![0u64; n];
697 let mut sort_indices = vec![0u32; n];
698
699 let (node_count, root) = build_bvh_recursive(
700 &prims,
701 &mut nodes,
702 &mut prim_indices,
703 &mut morton_codes,
704 &mut sort_indices,
705 )
706 .unwrap();
707
708 let query = make_aabb([1.5, 1.5, 0.0], [3.5, 2.5, 1.0]);
709 let mut bvh_out = vec![0u32; n];
710 let mut stack = vec![0u32; MAX_BVH_DEPTH * 2];
711 let bvh_count = query_overlap(
712 &nodes,
713 &prims,
714 &prim_indices,
715 root,
716 node_count,
717 &query,
718 &mut bvh_out,
719 &mut stack,
720 )
721 .unwrap();
722
723 let mut brute_out: Vec<u32> = Vec::new();
725 for (i, p) in prims.iter().enumerate() {
726 if p.overlaps(&query) {
727 brute_out.push(i as u32);
728 }
729 }
730
731 let mut bvh_sorted: Vec<u32> = bvh_out[..bvh_count].to_vec();
733 bvh_sorted.sort_unstable();
734 let mut brute_sorted = brute_out.clone();
735 brute_sorted.sort_unstable();
736 assert_eq!(bvh_sorted, brute_sorted, "BVH and brute force must agree");
737 }
738
739 #[test]
740 fn empty_bvh_returns_empty() {
741 let prims: Vec<Aabb> = vec![];
742 let mut nodes = vec![BvhNode::default(); 1];
743 let mut prim_indices = vec![0u32; 1];
744 let mut morton_codes = vec![0u64; 1];
745 let mut sort_indices = vec![0u32; 1];
746
747 let (node_count, _) = build_bvh_recursive(
748 &prims,
749 &mut nodes,
750 &mut prim_indices,
751 &mut morton_codes,
752 &mut sort_indices,
753 )
754 .unwrap();
755
756 assert_eq!(node_count, 0);
757 }
758
759 #[test]
760 fn single_primitive_bvh() {
761 let prims = vec![make_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])];
762 let n = prims.len();
763 let mut nodes = vec![BvhNode::default(); 2 * n];
764 let mut prim_indices = vec![0u32; n];
765 let mut morton_codes = vec![0u64; n];
766 let mut sort_indices = vec![0u32; n];
767
768 let (node_count, root) = build_bvh_recursive(
769 &prims,
770 &mut nodes,
771 &mut prim_indices,
772 &mut morton_codes,
773 &mut sort_indices,
774 )
775 .unwrap();
776
777 assert_eq!(node_count, 1);
778 let node = &nodes[root];
779 assert_eq!(node.right_or_count, 1); }
781}