Skip to main content

oxiphysics_collision/dbvt/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxiphysics_core::math::Vec3;
6
7use super::functions::{FAT_MARGIN, NodeIdx};
8
9/// A node in the dynamic BVH tree.
10pub(super) struct DbvtNode {
11    /// Bounding box of this node (fat for leaves, tight for internal nodes).
12    pub(super) aabb: BvhAabb,
13    /// Parent index, or `None` for the root.
14    pub(super) parent: Option<NodeIdx>,
15    /// Left child (internal nodes only).
16    pub(super) left: Option<NodeIdx>,
17    /// Right child (internal nodes only).
18    pub(super) right: Option<NodeIdx>,
19    /// Leaf payload — `None` for internal nodes.
20    pub(super) data: Option<u32>,
21    /// Sub-tree height (0 for leaves).
22    pub(super) height: i32,
23}
24impl DbvtNode {
25    fn leaf(aabb: BvhAabb, data: u32) -> Self {
26        Self {
27            aabb,
28            parent: None,
29            left: None,
30            right: None,
31            data: Some(data),
32            height: 0,
33        }
34    }
35    fn internal(aabb: BvhAabb, left: NodeIdx, right: NodeIdx) -> Self {
36        Self {
37            aabb,
38            parent: None,
39            left: Some(left),
40            right: Some(right),
41            data: None,
42            height: 1,
43        }
44    }
45    pub(crate) fn is_leaf(&self) -> bool {
46        self.data.is_some()
47    }
48}
49/// One entry in a k-nearest result: (leaf data, squared distance to query point).
50#[derive(Debug, Clone)]
51pub struct NearestLeaf {
52    /// Leaf payload data.
53    pub data: u32,
54    /// Squared distance from the query point to the AABB center of the leaf.
55    pub dist_sq: f64,
56}
57/// Axis-Aligned Bounding Box used by the dynamic BVH tree.
58#[derive(Debug, Clone, Copy)]
59pub struct BvhAabb {
60    /// Minimum corner of the bounding box.
61    pub min: Vec3,
62    /// Maximum corner of the bounding box.
63    pub max: Vec3,
64}
65impl BvhAabb {
66    /// Create a new AABB from min/max corners.
67    #[inline]
68    pub fn new(min: Vec3, max: Vec3) -> Self {
69        Self { min, max }
70    }
71    /// Create a zero-size AABB at a single point.
72    #[inline]
73    pub fn point(p: Vec3) -> Self {
74        Self { min: p, max: p }
75    }
76    /// Create an AABB from a sphere (center + radius).
77    #[inline]
78    pub fn from_sphere(center: Vec3, radius: f64) -> Self {
79        let r = Vec3::new(radius, radius, radius);
80        Self {
81            min: center - r,
82            max: center + r,
83        }
84    }
85    /// Center of the AABB.
86    #[inline]
87    pub fn center(&self) -> Vec3 {
88        (self.min + self.max) * 0.5
89    }
90    /// Half-extents (half-widths along each axis).
91    #[inline]
92    pub fn half_extents(&self) -> Vec3 {
93        (self.max - self.min) * 0.5
94    }
95    /// Surface area: `2*(dx*dy + dy*dz + dz*dx)`.
96    #[inline]
97    pub fn surface_area(&self) -> f64 {
98        let d = self.max - self.min;
99        2.0 * (d.x * d.y + d.y * d.z + d.z * d.x)
100    }
101    /// Volume of the AABB.
102    #[inline]
103    pub fn volume(&self) -> f64 {
104        let d = self.max - self.min;
105        d.x * d.y * d.z
106    }
107    /// Return `true` if `self` fully contains `other`.
108    #[inline]
109    pub fn contains(&self, other: &Self) -> bool {
110        self.min.x <= other.min.x
111            && self.min.y <= other.min.y
112            && self.min.z <= other.min.z
113            && self.max.x >= other.max.x
114            && self.max.y >= other.max.y
115            && self.max.z >= other.max.z
116    }
117    /// Return `true` if `self` overlaps `other`.
118    #[inline]
119    pub fn intersects(&self, other: &Self) -> bool {
120        self.min.x <= other.max.x
121            && self.max.x >= other.min.x
122            && self.min.y <= other.max.y
123            && self.max.y >= other.min.y
124            && self.min.z <= other.max.z
125            && self.max.z >= other.min.z
126    }
127    /// Smallest AABB that contains both `a` and `b`.
128    #[inline]
129    pub fn merge(a: &Self, b: &Self) -> Self {
130        Self {
131            min: Vec3::new(
132                a.min.x.min(b.min.x),
133                a.min.y.min(b.min.y),
134                a.min.z.min(b.min.z),
135            ),
136            max: Vec3::new(
137                a.max.x.max(b.max.x),
138                a.max.y.max(b.max.y),
139                a.max.z.max(b.max.z),
140            ),
141        }
142    }
143    /// Expand this AABB uniformly by `margin` in every direction.
144    #[inline]
145    pub fn expand(&self, margin: f64) -> Self {
146        let d = Vec3::new(margin, margin, margin);
147        Self {
148            min: self.min - d,
149            max: self.max + d,
150        }
151    }
152}
153impl BvhAabb {
154    /// Return the axis (0=X, 1=Y, 2=Z) along which the AABB is widest.
155    #[inline]
156    pub fn longest_axis(&self) -> usize {
157        let d = self.max - self.min;
158        if d.x >= d.y && d.x >= d.z {
159            0
160        } else if d.y >= d.z {
161            1
162        } else {
163            2
164        }
165    }
166    /// Return `true` if `self` and `other` are the same AABB (within `eps`).
167    #[inline]
168    pub fn approx_eq(&self, other: &Self, eps: f64) -> bool {
169        (self.min.x - other.min.x).abs() <= eps
170            && (self.min.y - other.min.y).abs() <= eps
171            && (self.min.z - other.min.z).abs() <= eps
172            && (self.max.x - other.max.x).abs() <= eps
173            && (self.max.y - other.max.y).abs() <= eps
174            && (self.max.z - other.max.z).abs() <= eps
175    }
176    /// Squared distance from `point` to the nearest point on this AABB.
177    ///
178    /// Returns `0.0` if `point` is inside the AABB.
179    #[inline]
180    pub fn point_dist_sq(&self, point: Vec3) -> f64 {
181        let dx = (point.x - self.min.x.max(self.max.x.min(point.x))).powi(2);
182        let dy = (point.y - self.min.y.max(self.max.y.min(point.y))).powi(2);
183        let dz = (point.z - self.min.z.max(self.max.z.min(point.z))).powi(2);
184        dx + dy + dz
185    }
186    /// Translate the AABB by `offset`.
187    #[inline]
188    pub fn translate(&self, offset: Vec3) -> Self {
189        Self {
190            min: self.min + offset,
191            max: self.max + offset,
192        }
193    }
194    /// Scale the AABB uniformly around its center by `factor`.
195    #[inline]
196    pub fn scale(&self, factor: f64) -> Self {
197        let c = self.center();
198        let he = self.half_extents() * factor;
199        Self {
200            min: c - he,
201            max: c + he,
202        }
203    }
204}
205impl BvhAabb {
206    /// Test a ray `origin + t*dir` against this AABB for `t ∈ [t_min, t_max]`.
207    ///
208    /// Returns the entry `t` value if the ray hits, or `None` otherwise.
209    pub fn ray_intersect(&self, origin: Vec3, dir: Vec3, t_min: f64, t_max: f64) -> Option<f64> {
210        let orig = [origin.x, origin.y, origin.z];
211        let d = [dir.x, dir.y, dir.z];
212        let mn = [self.min.x, self.min.y, self.min.z];
213        let mx = [self.max.x, self.max.y, self.max.z];
214        let mut tmin = t_min;
215        let mut tmax = t_max;
216        for i in 0..3 {
217            if d[i].abs() < 1e-300 {
218                if orig[i] < mn[i] || orig[i] > mx[i] {
219                    return None;
220                }
221            } else {
222                let inv = 1.0 / d[i];
223                let t1 = (mn[i] - orig[i]) * inv;
224                let t2 = (mx[i] - orig[i]) * inv;
225                let (ta, tb) = if t1 <= t2 { (t1, t2) } else { (t2, t1) };
226                tmin = tmin.max(ta);
227                tmax = tmax.min(tb);
228                if tmin > tmax {
229                    return None;
230                }
231            }
232        }
233        if tmax < t_min {
234            return None;
235        }
236        Some(tmin.max(t_min))
237    }
238}
239/// Statistics collected from a DBVT query pass.
240#[derive(Debug, Clone, Default)]
241pub struct DbvtStats {
242    /// Number of leaf nodes in the tree.
243    pub leaf_count: usize,
244    /// Total node count (leaves + internal).
245    pub node_count: usize,
246    /// Tree height.
247    pub height: i32,
248    /// Surface area of the root AABB.
249    pub root_surface_area: f64,
250    /// Number of overlap pairs found by last `query_overlapping_pairs`.
251    pub overlap_pairs: usize,
252}
253/// A view frustum represented by six half-space planes (inward normals).
254///
255/// Each plane `(n, d)` passes the test when `n · p >= d`.
256#[derive(Debug, Clone)]
257pub struct BvhFrustum {
258    /// Six planes: (inward_normal, offset).
259    pub planes: [(Vec3, f64); 6],
260}
261impl BvhFrustum {
262    /// Construct a frustum from six plane definitions.
263    pub fn new(planes: [(Vec3, f64); 6]) -> Self {
264        Self { planes }
265    }
266    /// Test whether an AABB is potentially inside (not fully outside) the frustum.
267    pub fn intersects_aabb(&self, aabb: &BvhAabb) -> bool {
268        for &(n, d) in &self.planes {
269            let px = if n.x >= 0.0 { aabb.max.x } else { aabb.min.x };
270            let py = if n.y >= 0.0 { aabb.max.y } else { aabb.min.y };
271            let pz = if n.z >= 0.0 { aabb.max.z } else { aabb.min.z };
272            let dot = n.x * px + n.y * py + n.z * pz;
273            if dot < d {
274                return false;
275            }
276        }
277        true
278    }
279}
280/// A capsule shape: a line segment swept by a sphere.
281#[derive(Debug, Clone, Copy)]
282pub struct BvhCapsule {
283    /// Start point of the central segment.
284    pub start: Vec3,
285    /// End point of the central segment.
286    pub end: Vec3,
287    /// Radius of the capsule.
288    pub radius: f64,
289}
290/// Dynamic AABB Tree (DBVT) for broadphase collision detection.
291///
292/// Supports incremental `insert`, `remove`, and `update` operations.
293/// Uses the surface-area heuristic (SAH) to choose the best sibling when
294/// inserting a new leaf.
295pub struct DynamicBvh {
296    pub(super) nodes: Vec<DbvtNode>,
297    pub(super) root: Option<NodeIdx>,
298    pub(super) free_list: Vec<NodeIdx>,
299}
300impl DynamicBvh {
301    /// Create a new, empty dynamic BVH.
302    pub fn new() -> Self {
303        Self {
304            nodes: Vec::new(),
305            root: None,
306            free_list: Vec::new(),
307        }
308    }
309    fn alloc_node(&mut self, node: DbvtNode) -> NodeIdx {
310        if let Some(idx) = self.free_list.pop() {
311            self.nodes[idx] = node;
312            idx
313        } else {
314            let idx = self.nodes.len();
315            self.nodes.push(node);
316            idx
317        }
318    }
319    fn free_node(&mut self, idx: NodeIdx) {
320        self.nodes[idx].parent = None;
321        self.nodes[idx].left = None;
322        self.nodes[idx].right = None;
323        self.nodes[idx].data = None;
324        self.free_list.push(idx);
325    }
326    /// Insert a leaf AABB with associated `data`. Returns the leaf node index.
327    ///
328    /// The leaf is stored with a fat AABB (expanded by `FAT_MARGIN`) to
329    /// reduce the frequency of re-insertions when the object moves slightly.
330    pub fn insert(&mut self, aabb: BvhAabb, data: u32) -> NodeIdx {
331        let fat_aabb = aabb.expand(FAT_MARGIN);
332        let leaf_idx = self.alloc_node(DbvtNode::leaf(fat_aabb, data));
333        if self.root.is_none() {
334            self.root = Some(leaf_idx);
335            return leaf_idx;
336        }
337        let sibling = self.find_best_sibling(leaf_idx);
338        let old_parent = self.nodes[sibling].parent;
339        let new_aabb = BvhAabb::merge(&self.nodes[sibling].aabb, &self.nodes[leaf_idx].aabb);
340        let new_parent_idx = self.alloc_node(DbvtNode::internal(new_aabb, sibling, leaf_idx));
341        self.nodes[new_parent_idx].parent = old_parent;
342        self.nodes[sibling].parent = Some(new_parent_idx);
343        self.nodes[leaf_idx].parent = Some(new_parent_idx);
344        match old_parent {
345            None => {
346                self.root = Some(new_parent_idx);
347            }
348            Some(gp) => {
349                if self.nodes[gp].left == Some(sibling) {
350                    self.nodes[gp].left = Some(new_parent_idx);
351                } else {
352                    self.nodes[gp].right = Some(new_parent_idx);
353                }
354            }
355        }
356        self.refit(new_parent_idx);
357        leaf_idx
358    }
359    /// Remove a leaf node by its index.
360    ///
361    /// # Panics
362    /// Panics if `leaf` is not a leaf node.
363    pub fn remove(&mut self, leaf: NodeIdx) {
364        assert!(self.nodes[leaf].is_leaf(), "remove: node is not a leaf");
365        let parent = self.nodes[leaf].parent;
366        match parent {
367            None => {
368                debug_assert_eq!(self.root, Some(leaf));
369                self.root = None;
370                self.free_node(leaf);
371            }
372            Some(parent_idx) => {
373                let grandparent = self.nodes[parent_idx].parent;
374                let sibling = if self.nodes[parent_idx].left == Some(leaf) {
375                    self.nodes[parent_idx]
376                        .right
377                        .expect("internal node has no right child")
378                } else {
379                    self.nodes[parent_idx]
380                        .left
381                        .expect("internal node has no left child")
382                };
383                self.nodes[sibling].parent = grandparent;
384                match grandparent {
385                    None => {
386                        self.root = Some(sibling);
387                    }
388                    Some(gp) => {
389                        if self.nodes[gp].left == Some(parent_idx) {
390                            self.nodes[gp].left = Some(sibling);
391                        } else {
392                            self.nodes[gp].right = Some(sibling);
393                        }
394                        self.refit(gp);
395                    }
396                }
397                self.free_node(parent_idx);
398                self.free_node(leaf);
399            }
400        }
401    }
402    /// Update an existing leaf's AABB.
403    ///
404    /// If `new_aabb` still fits within the leaf's current fat AABB, the tree
405    /// is unchanged and `false` is returned. Otherwise the leaf is removed and
406    /// reinserted with a fresh fat AABB, and `true` is returned.
407    pub fn update(&mut self, leaf: NodeIdx, new_aabb: BvhAabb) -> bool {
408        assert!(self.nodes[leaf].is_leaf(), "update: node is not a leaf");
409        if self.nodes[leaf].aabb.contains(&new_aabb) {
410            return false;
411        }
412        let data = self.nodes[leaf].data.expect("leaf must have data");
413        self.remove(leaf);
414        self.insert(new_aabb, data);
415        true
416    }
417    /// Return all leaf-data pairs `(a, b)` where `a < b` and the leaves
418    /// overlap. Uses a stack-based traversal.
419    pub fn query_overlapping_pairs(&self) -> Vec<(u32, u32)> {
420        let mut pairs = Vec::new();
421        let Some(root) = self.root else {
422            return pairs;
423        };
424        let mut leaves: Vec<NodeIdx> = Vec::new();
425        let mut stack = vec![root];
426        while let Some(idx) = stack.pop() {
427            let node = &self.nodes[idx];
428            if node.is_leaf() {
429                leaves.push(idx);
430            } else {
431                if let Some(l) = node.left {
432                    stack.push(l);
433                }
434                if let Some(r) = node.right {
435                    stack.push(r);
436                }
437            }
438        }
439        for (i, &la) in leaves.iter().enumerate() {
440            let aabb_a = self.nodes[la].aabb;
441            let data_a = match self.nodes[la].data {
442                Some(d) => d,
443                None => continue,
444            };
445            for &lb in &leaves[(i + 1)..] {
446                if aabb_a.intersects(&self.nodes[lb].aabb) {
447                    let Some(data_b) = self.nodes[lb].data else {
448                        continue;
449                    };
450                    let (x, y) = if data_a < data_b {
451                        (data_a, data_b)
452                    } else {
453                        (data_b, data_a)
454                    };
455                    pairs.push((x, y));
456                }
457            }
458        }
459        pairs
460    }
461    /// Return the data of all leaves whose fat AABB overlaps `aabb`.
462    pub fn query_aabb(&self, aabb: &BvhAabb) -> Vec<u32> {
463        let mut results = Vec::new();
464        if let Some(root) = self.root {
465            self.query_aabb_recursive(root, aabb, &mut results);
466        }
467        results
468    }
469    /// Return the data of all leaves whose fat AABB contains `point`.
470    pub fn query_point(&self, point: Vec3) -> Vec<u32> {
471        let pt_aabb = BvhAabb::point(point);
472        self.query_aabb(&pt_aabb)
473    }
474    /// Number of leaf nodes currently in the tree.
475    pub fn n_leaves(&self) -> usize {
476        self.nodes
477            .iter()
478            .enumerate()
479            .filter(|(idx, n)| n.is_leaf() && !self.free_list.contains(idx))
480            .count()
481    }
482    /// Total number of nodes (leaves + internal) currently in the tree.
483    pub fn n_nodes(&self) -> usize {
484        self.nodes.len() - self.free_list.len()
485    }
486    /// Height of the tree (0 for empty, 0 for a single leaf).
487    pub fn height(&self) -> i32 {
488        match self.root {
489            None => 0,
490            Some(r) => self.nodes[r].height,
491        }
492    }
493    /// Validate tree invariants. Returns `true` if the tree is consistent.
494    ///
495    /// Checks:
496    /// - root has no parent
497    /// - all parent/child pointers are consistent
498    /// - internal node AABBs contain both children's AABBs
499    pub fn validate(&self) -> bool {
500        match self.root {
501            None => true,
502            Some(root) => {
503                if self.nodes[root].parent.is_some() {
504                    return false;
505                }
506                self.validate_node(root)
507            }
508        }
509    }
510    /// Find the best sibling for a new leaf using a greedy SAH traversal.
511    fn find_best_sibling(&self, leaf: NodeIdx) -> NodeIdx {
512        let leaf_sa = self.nodes[leaf].aabb.surface_area();
513        let Some(root) = self.root else {
514            return leaf;
515        };
516        let mut best = root;
517        let mut best_cost =
518            BvhAabb::merge(&self.nodes[root].aabb, &self.nodes[leaf].aabb).surface_area();
519        let mut stack: Vec<(NodeIdx, f64)> = vec![(root, 0.0)];
520        while let Some((idx, inherited_cost)) = stack.pop() {
521            let node = &self.nodes[idx];
522            let direct_cost = BvhAabb::merge(&node.aabb, &self.nodes[leaf].aabb).surface_area();
523            let cost = direct_cost + inherited_cost;
524            if cost < best_cost {
525                best_cost = cost;
526                best = idx;
527            }
528            if !node.is_leaf() {
529                let child_inherited = inherited_cost + direct_cost - node.aabb.surface_area();
530                let lower_bound = leaf_sa + child_inherited;
531                if lower_bound < best_cost {
532                    if let Some(l) = node.left {
533                        stack.push((l, child_inherited));
534                    }
535                    if let Some(r) = node.right {
536                        stack.push((r, child_inherited));
537                    }
538                }
539            }
540        }
541        best
542    }
543    /// Walk from `start` to the root, refitting AABBs and updating heights.
544    fn refit(&mut self, start: NodeIdx) {
545        let mut idx = Some(start);
546        while let Some(i) = idx {
547            let node = &self.nodes[i];
548            if node.is_leaf() {
549                idx = node.parent;
550                continue;
551            }
552            let (Some(left), Some(right)) = (node.left, node.right) else {
553                idx = node.parent;
554                continue;
555            };
556            let merged = BvhAabb::merge(&self.nodes[left].aabb, &self.nodes[right].aabb);
557            let height = 1 + self.nodes[left].height.max(self.nodes[right].height);
558            self.nodes[i].aabb = merged;
559            self.nodes[i].height = height;
560            idx = self.nodes[i].parent;
561        }
562    }
563    fn query_aabb_recursive(&self, idx: NodeIdx, aabb: &BvhAabb, out: &mut Vec<u32>) {
564        let node = &self.nodes[idx];
565        if !node.aabb.intersects(aabb) {
566            return;
567        }
568        if node.is_leaf() {
569            if let Some(d) = node.data {
570                out.push(d);
571            }
572        } else {
573            if let Some(l) = node.left {
574                self.query_aabb_recursive(l, aabb, out);
575            }
576            if let Some(r) = node.right {
577                self.query_aabb_recursive(r, aabb, out);
578            }
579        }
580    }
581    fn validate_node(&self, idx: NodeIdx) -> bool {
582        let node = &self.nodes[idx];
583        if node.is_leaf() {
584            return true;
585        }
586        let left = match node.left {
587            Some(l) => l,
588            None => return false,
589        };
590        let right = match node.right {
591            Some(r) => r,
592            None => return false,
593        };
594        if self.nodes[left].parent != Some(idx) {
595            return false;
596        }
597        if self.nodes[right].parent != Some(idx) {
598            return false;
599        }
600        if !node.aabb.contains(&self.nodes[left].aabb) {
601            return false;
602        }
603        if !node.aabb.contains(&self.nodes[right].aabb) {
604            return false;
605        }
606        self.validate_node(left) && self.validate_node(right)
607    }
608}
609impl DynamicBvh {
610    /// Collect tree statistics.
611    pub fn stats(&self) -> DbvtStats {
612        DbvtStats {
613            leaf_count: self.n_leaves(),
614            node_count: self.n_nodes(),
615            height: self.height(),
616            root_surface_area: self
617                .root
618                .map(|r| self.nodes[r].aabb.surface_area())
619                .unwrap_or(0.0),
620            overlap_pairs: 0,
621        }
622    }
623    /// Refit all internal-node AABBs from leaves upward (full refit pass).
624    ///
625    /// Useful after bulk updates.
626    pub fn refit_all(&mut self) {
627        if let Some(root) = self.root {
628            self.refit(root);
629        }
630    }
631    /// Ray cast against all leaves; returns data of all leaves hit.
632    ///
633    /// `origin` + `dir * t` for `t ∈ [t_min, t_max]`.
634    pub fn ray_cast(&self, origin: Vec3, dir: Vec3, t_min: f64, t_max: f64) -> Vec<(u32, f64)> {
635        let mut hits = Vec::new();
636        if let Some(root) = self.root {
637            self.ray_cast_recursive(root, origin, dir, t_min, t_max, &mut hits);
638        }
639        hits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
640        hits
641    }
642    fn ray_cast_recursive(
643        &self,
644        idx: NodeIdx,
645        origin: Vec3,
646        dir: Vec3,
647        t_min: f64,
648        t_max: f64,
649        hits: &mut Vec<(u32, f64)>,
650    ) {
651        let node = &self.nodes[idx];
652        if let Some(t) = Self::ray_aabb_slab(origin, dir, node.aabb, t_min, t_max) {
653            if node.is_leaf() {
654                if let Some(d) = node.data {
655                    hits.push((d, t));
656                }
657            } else {
658                if let Some(l) = node.left {
659                    self.ray_cast_recursive(l, origin, dir, t_min, t_max, hits);
660                }
661                if let Some(r) = node.right {
662                    self.ray_cast_recursive(r, origin, dir, t_min, t_max, hits);
663                }
664            }
665        }
666    }
667    /// Ray vs AABB slab intersection; returns entry `t` or `None`.
668    fn ray_aabb_slab(
669        origin: Vec3,
670        dir: Vec3,
671        aabb: BvhAabb,
672        t_min: f64,
673        t_max: f64,
674    ) -> Option<f64> {
675        let mut tmin = t_min;
676        let mut tmax = t_max;
677        let orig = [origin.x, origin.y, origin.z];
678        let d = [dir.x, dir.y, dir.z];
679        let mn = [aabb.min.x, aabb.min.y, aabb.min.z];
680        let mx = [aabb.max.x, aabb.max.y, aabb.max.z];
681        for i in 0..3 {
682            if d[i].abs() < 1e-300 {
683                if orig[i] < mn[i] || orig[i] > mx[i] {
684                    return None;
685                }
686            } else {
687                let inv = 1.0 / d[i];
688                let t1 = (mn[i] - orig[i]) * inv;
689                let t2 = (mx[i] - orig[i]) * inv;
690                let (ta, tb) = if t1 <= t2 { (t1, t2) } else { (t2, t1) };
691                tmin = tmin.max(ta);
692                tmax = tmax.min(tb);
693                if tmin > tmax {
694                    return None;
695                }
696            }
697        }
698        if tmax < t_min {
699            return None;
700        }
701        Some(tmin.max(t_min))
702    }
703    /// Overlap query: return data of all leaves that overlap `query_aabb` and
704    /// whose data value satisfies `filter`.
705    ///
706    /// This is an enhanced version of `query_aabb` that supports a predicate.
707    pub fn query_aabb_filtered(&self, aabb: &BvhAabb, filter: impl Fn(u32) -> bool) -> Vec<u32> {
708        let mut results = Vec::new();
709        if let Some(root) = self.root {
710            self.query_aabb_filtered_recursive(root, aabb, &filter, &mut results);
711        }
712        results
713    }
714    fn query_aabb_filtered_recursive(
715        &self,
716        idx: NodeIdx,
717        aabb: &BvhAabb,
718        filter: &impl Fn(u32) -> bool,
719        out: &mut Vec<u32>,
720    ) {
721        let node = &self.nodes[idx];
722        if !node.aabb.intersects(aabb) {
723            return;
724        }
725        if node.is_leaf() {
726            if let Some(d) = node.data
727                && filter(d)
728            {
729                out.push(d);
730            }
731        } else {
732            if let Some(l) = node.left {
733                self.query_aabb_filtered_recursive(l, aabb, filter, out);
734            }
735            if let Some(r) = node.right {
736                self.query_aabb_filtered_recursive(r, aabb, filter, out);
737            }
738        }
739    }
740    /// Bulk insert many `(aabb, data)` pairs and return their leaf indices.
741    pub fn insert_bulk(&mut self, items: &[(BvhAabb, u32)]) -> Vec<NodeIdx> {
742        items
743            .iter()
744            .map(|(aabb, data)| self.insert(*aabb, *data))
745            .collect()
746    }
747    /// Remove all leaves and reset the tree to empty.
748    pub fn clear(&mut self) {
749        self.nodes.clear();
750        self.free_list.clear();
751        self.root = None;
752    }
753    /// Collect all leaf data values in the tree (arbitrary order).
754    pub fn all_leaf_data(&self) -> Vec<u32> {
755        self.nodes
756            .iter()
757            .enumerate()
758            .filter(|(idx, n)| n.is_leaf() && !self.free_list.contains(idx))
759            .filter_map(|(_, n)| n.data)
760            .collect()
761    }
762    /// Count overlapping pairs and return both the pairs and stats.
763    pub fn query_pairs_with_stats(&self) -> (Vec<(u32, u32)>, DbvtStats) {
764        let pairs = self.query_overlapping_pairs();
765        let mut s = self.stats();
766        s.overlap_pairs = pairs.len();
767        (pairs, s)
768    }
769}
770impl DynamicBvh {
771    /// Compute BVH quality metrics via a single DFS traversal.
772    ///
773    /// Returns `None` if the tree is empty.
774    pub fn quality_metrics(&self) -> Option<BvhQuality> {
775        let root = self.root?;
776        let root_sa = self.nodes[root].aabb.surface_area();
777        if root_sa <= 0.0 {
778            return None;
779        }
780        let mut sah_cost = 0.0_f64;
781        let mut max_depth = 0usize;
782        let mut min_leaf_depth = usize::MAX;
783        let mut total_leaf_depth = 0usize;
784        let mut leaf_count = 0usize;
785        let mut internal_count = 0usize;
786        let mut stack: Vec<(NodeIdx, usize)> = vec![(root, 0)];
787        while let Some((idx, depth)) = stack.pop() {
788            let node = &self.nodes[idx];
789            if depth > max_depth {
790                max_depth = depth;
791            }
792            if node.is_leaf() {
793                leaf_count += 1;
794                total_leaf_depth += depth;
795                if depth < min_leaf_depth {
796                    min_leaf_depth = depth;
797                }
798            } else {
799                internal_count += 1;
800                let sa = node.aabb.surface_area();
801                sah_cost += sa / root_sa;
802                if let Some(l) = node.left {
803                    stack.push((l, depth + 1));
804                }
805                if let Some(r) = node.right {
806                    stack.push((r, depth + 1));
807                }
808            }
809        }
810        let avg_leaf_depth = if leaf_count > 0 {
811            total_leaf_depth as f64 / leaf_count as f64
812        } else {
813            0.0
814        };
815        let min_leaf_depth_out = if min_leaf_depth == usize::MAX {
816            0
817        } else {
818            min_leaf_depth
819        };
820        Some(BvhQuality {
821            sah_cost,
822            max_depth,
823            min_leaf_depth: min_leaf_depth_out,
824            avg_leaf_depth,
825            leaf_count,
826            internal_count,
827        })
828    }
829    /// Return `true` if the SAH cost is above `threshold` (tree quality degraded).
830    ///
831    /// Useful as a trigger to rebuild the tree from scratch.
832    pub fn quality_degraded(&self, threshold: f64) -> bool {
833        match self.quality_metrics() {
834            Some(q) => q.sah_cost > threshold,
835            None => false,
836        }
837    }
838}
839impl DynamicBvh {
840    /// Return the data of all leaves potentially visible inside `frustum`.
841    ///
842    /// Uses a stack-based traversal that prunes subtrees whose AABB is
843    /// fully outside any frustum plane.
844    pub fn frustum_query(&self, frustum: &BvhFrustum) -> Vec<u32> {
845        let mut results = Vec::new();
846        let Some(root) = self.root else {
847            return results;
848        };
849        let mut stack = vec![root];
850        while let Some(idx) = stack.pop() {
851            let node = &self.nodes[idx];
852            if !frustum.intersects_aabb(&node.aabb) {
853                continue;
854            }
855            if node.is_leaf() {
856                if let Some(d) = node.data {
857                    results.push(d);
858                }
859            } else {
860                if let Some(l) = node.left {
861                    stack.push(l);
862                }
863                if let Some(r) = node.right {
864                    stack.push(r);
865                }
866            }
867        }
868        results
869    }
870}
871impl DynamicBvh {
872    /// Return the `k` closest leaves (by AABB-center distance) to `point`.
873    ///
874    /// Result is sorted nearest-first.
875    pub fn k_nearest(&self, point: Vec3, k: usize) -> Vec<NearestLeaf> {
876        if k == 0 {
877            return Vec::new();
878        }
879        let mut candidates: Vec<NearestLeaf> = self
880            .nodes
881            .iter()
882            .enumerate()
883            .filter(|(idx, n)| n.is_leaf() && !self.free_list.contains(idx))
884            .filter_map(|(_, n)| {
885                let d = n.data?;
886                let c = n.aabb.center();
887                let dx = c.x - point.x;
888                let dy = c.y - point.y;
889                let dz = c.z - point.z;
890                Some(NearestLeaf {
891                    data: d,
892                    dist_sq: dx * dx + dy * dy + dz * dz,
893                })
894            })
895            .collect();
896        candidates.sort_by(|a, b| {
897            a.dist_sq
898                .partial_cmp(&b.dist_sq)
899                .unwrap_or(std::cmp::Ordering::Equal)
900        });
901        candidates.truncate(k);
902        candidates
903    }
904}
905impl DynamicBvh {
906    /// Serialize the entire tree into a flat `Vec`f64`.
907    ///
908    /// Format per node (14 values):
909    /// `\[min_x, min_y, min_z, max_x, max_y, max_z,
910    ///   parent_or_neg1, left_or_neg1, right_or_neg1,
911    ///   data_or_neg1, height, is_leaf, is_on_free_list, PADDING]`
912    ///
913    /// The first element of the buffer is the total number of nodes (as f64).
914    pub fn serialize(&self) -> Vec<f64> {
915        const STRIDE: usize = 14;
916        let mut buf = Vec::with_capacity(1 + self.nodes.len() * STRIDE);
917        buf.push(self.nodes.len() as f64);
918        for (idx, node) in self.nodes.iter().enumerate() {
919            buf.push(node.aabb.min.x);
920            buf.push(node.aabb.min.y);
921            buf.push(node.aabb.min.z);
922            buf.push(node.aabb.max.x);
923            buf.push(node.aabb.max.y);
924            buf.push(node.aabb.max.z);
925            buf.push(node.parent.map(|p| p as f64).unwrap_or(-1.0));
926            buf.push(node.left.map(|l| l as f64).unwrap_or(-1.0));
927            buf.push(node.right.map(|r| r as f64).unwrap_or(-1.0));
928            buf.push(node.data.map(|d| d as f64).unwrap_or(-1.0));
929            buf.push(node.height as f64);
930            buf.push(if node.is_leaf() { 1.0 } else { 0.0 });
931            buf.push(if self.free_list.contains(&idx) {
932                1.0
933            } else {
934                0.0
935            });
936            buf.push(0.0);
937        }
938        buf
939    }
940    /// Return the root node index, or `None` if the tree is empty.
941    pub fn root(&self) -> Option<NodeIdx> {
942        self.root
943    }
944}
945impl DynamicBvh {
946    /// Update leaf `leaf` using a new tight AABB expanded in the direction of
947    /// `velocity * dt` (speculative / predicted motion).
948    ///
949    /// The resulting fat AABB covers both the current and predicted positions.
950    /// Returns `true` if the tree was modified (re-insertion occurred).
951    pub fn move_proxy(
952        &mut self,
953        leaf: NodeIdx,
954        new_aabb: BvhAabb,
955        velocity: Vec3,
956        dt: f64,
957    ) -> bool {
958        assert!(self.nodes[leaf].is_leaf(), "move_proxy: node is not a leaf");
959        let displacement = Vec3::new(velocity.x * dt, velocity.y * dt, velocity.z * dt);
960        let predicted = BvhAabb {
961            min: Vec3::new(
962                new_aabb.min.x + displacement.x.min(0.0),
963                new_aabb.min.y + displacement.y.min(0.0),
964                new_aabb.min.z + displacement.z.min(0.0),
965            ),
966            max: Vec3::new(
967                new_aabb.max.x + displacement.x.max(0.0),
968                new_aabb.max.y + displacement.y.max(0.0),
969                new_aabb.max.z + displacement.z.max(0.0),
970            ),
971        };
972        let swept = BvhAabb::merge(&new_aabb, &predicted);
973        if self.nodes[leaf].aabb.contains(&swept) {
974            return false;
975        }
976        let data = self.nodes[leaf].data.expect("leaf must have data");
977        self.remove(leaf);
978        self.insert(swept, data);
979        true
980    }
981}
982impl DynamicBvh {
983    /// Attempt one SAH-improving rotation at node `idx`.
984    ///
985    /// Tries swapping each grandchild with its uncle; keeps the swap
986    /// only if it strictly reduces the total surface area of the two
987    /// affected internal nodes.
988    ///
989    /// Returns `true` if a rotation was performed.
990    pub fn try_rotate(&mut self, idx: NodeIdx) -> bool {
991        let node = &self.nodes[idx];
992        if node.is_leaf() {
993            return false;
994        }
995        let left = match node.left {
996            Some(l) => l,
997            None => return false,
998        };
999        let right = match node.right {
1000            Some(r) => r,
1001            None => return false,
1002        };
1003        let current_cost =
1004            self.nodes[left].aabb.surface_area() + self.nodes[right].aabb.surface_area();
1005        let mut best_cost = current_cost;
1006        let mut best_rotation: Option<(NodeIdx, NodeIdx, NodeIdx)> = None;
1007        if !self.nodes[right].is_leaf() {
1008            let (Some(rl), Some(rr)) = (self.nodes[right].left, self.nodes[right].right) else {
1009                return false;
1010            };
1011            let new_right_sa =
1012                BvhAabb::merge(&self.nodes[left].aabb, &self.nodes[rr].aabb).surface_area();
1013            let candidate_cost = new_right_sa + self.nodes[left].aabb.surface_area();
1014            if candidate_cost < best_cost {
1015                best_cost = candidate_cost;
1016                best_rotation = Some((idx, left, rl));
1017            }
1018            let new_right_sa2 =
1019                BvhAabb::merge(&self.nodes[left].aabb, &self.nodes[rl].aabb).surface_area();
1020            let candidate_cost2 = new_right_sa2 + self.nodes[left].aabb.surface_area();
1021            if candidate_cost2 < best_cost {
1022                best_cost = candidate_cost2;
1023                best_rotation = Some((idx, left, rr));
1024            }
1025        }
1026        if !self.nodes[left].is_leaf() {
1027            let (Some(ll), Some(lr)) = (self.nodes[left].left, self.nodes[left].right) else {
1028                return false;
1029            };
1030            let new_left_sa =
1031                BvhAabb::merge(&self.nodes[right].aabb, &self.nodes[lr].aabb).surface_area();
1032            let candidate_cost = new_left_sa + self.nodes[right].aabb.surface_area();
1033            if candidate_cost < best_cost {
1034                best_cost = candidate_cost;
1035                best_rotation = Some((idx, right, ll));
1036            }
1037            let new_left_sa2 =
1038                BvhAabb::merge(&self.nodes[right].aabb, &self.nodes[ll].aabb).surface_area();
1039            let candidate_cost2 = new_left_sa2 + self.nodes[right].aabb.surface_area();
1040            if candidate_cost2 < best_cost {
1041                let _ = best_cost;
1042                best_rotation = Some((idx, right, lr));
1043            }
1044        }
1045        if let Some((parent_idx, child_in_parent, grandchild_to_swap)) = best_rotation {
1046            let Some(grandchild_parent) = self.nodes[grandchild_to_swap].parent else {
1047                return false;
1048            };
1049            let gp_left = self.nodes[grandchild_parent].left;
1050            let gp_right = self.nodes[grandchild_parent].right;
1051            let pl = self.nodes[parent_idx].left;
1052            let pr = self.nodes[parent_idx].right;
1053            if pl == Some(child_in_parent) {
1054                self.nodes[parent_idx].left = Some(grandchild_to_swap);
1055            } else if pr == Some(child_in_parent) {
1056                self.nodes[parent_idx].right = Some(grandchild_to_swap);
1057            }
1058            if gp_left == Some(grandchild_to_swap) {
1059                self.nodes[grandchild_parent].left = Some(child_in_parent);
1060            } else if gp_right == Some(grandchild_to_swap) {
1061                self.nodes[grandchild_parent].right = Some(child_in_parent);
1062            }
1063            self.nodes[grandchild_to_swap].parent = Some(parent_idx);
1064            self.nodes[child_in_parent].parent = Some(grandchild_parent);
1065            self.refit(grandchild_parent);
1066            self.refit(parent_idx);
1067            true
1068        } else {
1069            false
1070        }
1071    }
1072    /// Perform one bottom-up pass of SAH-improving rotations across the tree.
1073    ///
1074    /// Visits every non-leaf node from leaves upward and attempts `try_rotate`.
1075    pub fn optimize_rotations(&mut self) {
1076        let internal: Vec<NodeIdx> = self
1077            .nodes
1078            .iter()
1079            .enumerate()
1080            .filter(|(idx, n)| !n.is_leaf() && !self.free_list.contains(idx))
1081            .map(|(i, _)| i)
1082            .collect();
1083        let mut ordered = internal;
1084        ordered.sort_by_key(|&i| self.nodes[i].height);
1085        for idx in ordered {
1086            if !self.nodes[idx].is_leaf() {
1087                self.try_rotate(idx);
1088            }
1089        }
1090    }
1091}
1092impl DynamicBvh {
1093    /// Refit only the ancestors of a specific leaf node.
1094    ///
1095    /// More efficient than `refit_all` when only one leaf changed.
1096    pub fn refit_leaf_ancestors(&mut self, leaf: NodeIdx) {
1097        assert!(
1098            self.nodes[leaf].is_leaf(),
1099            "refit_leaf_ancestors: not a leaf"
1100        );
1101        if let Some(parent) = self.nodes[leaf].parent {
1102            self.refit(parent);
1103        }
1104    }
1105    /// Update a leaf's AABB *in place* without re-inserting it, then refit
1106    /// its ancestor chain.
1107    ///
1108    /// **Only use this when `new_aabb` is contained within the current fat AABB**;
1109    /// otherwise the tree's AABBs may become incorrect.  For unconstrained moves
1110    /// use [`DynamicBvh::update`] or [`DynamicBvh::move_proxy`].
1111    pub fn update_leaf_inplace(&mut self, leaf: NodeIdx, new_aabb: BvhAabb) {
1112        assert!(
1113            self.nodes[leaf].is_leaf(),
1114            "update_leaf_inplace: not a leaf"
1115        );
1116        self.nodes[leaf].aabb = new_aabb;
1117        self.refit_leaf_ancestors(leaf);
1118    }
1119    /// Compute the total SAH cost of the tree as a single scalar.
1120    ///
1121    /// Defined as `sum(SA(internal_node)) / SA(root)`.
1122    /// Lower is better; returns `0.0` for trees with 0 or 1 nodes.
1123    pub fn sah_cost(&self) -> f64 {
1124        let Some(root) = self.root else {
1125            return 0.0;
1126        };
1127        let root_sa = self.nodes[root].aabb.surface_area();
1128        if root_sa <= 0.0 {
1129            return 0.0;
1130        }
1131        let mut cost = 0.0_f64;
1132        let mut stack = vec![root];
1133        while let Some(idx) = stack.pop() {
1134            let node = &self.nodes[idx];
1135            if !node.is_leaf() {
1136                cost += node.aabb.surface_area() / root_sa;
1137                if let Some(l) = node.left {
1138                    stack.push(l);
1139                }
1140                if let Some(r) = node.right {
1141                    stack.push(r);
1142                }
1143            }
1144        }
1145        cost
1146    }
1147}
1148impl DynamicBvh {
1149    /// Compute the Surface Area Heuristic (SAH) cost of the entire tree.
1150    ///
1151    /// This is an alias for [`DynamicBvh::sah_cost`] provided under the name
1152    /// requested by the algorithm expansion spec so that callers can use either
1153    /// name without changing existing code.
1154    ///
1155    /// Returns `0.0` for an empty or degenerate tree.
1156    pub fn compute_sah_cost(&self) -> f64 {
1157        self.sah_cost()
1158    }
1159    /// Apply one round of balance-improving rotations to the entire tree.
1160    ///
1161    /// Walks every internal node in bottom-up order and attempts a single
1162    /// SAH-improving swap (see `try_rotate`).  Nodes are visited from
1163    /// shallowest to deepest so that parent improvements propagate before
1164    /// children are processed.
1165    ///
1166    /// Returns the number of rotations that were accepted.
1167    pub fn balance_rotation(&mut self) -> usize {
1168        let internal: Vec<NodeIdx> = self
1169            .nodes
1170            .iter()
1171            .enumerate()
1172            .filter(|(idx, n)| !n.is_leaf() && !self.free_list.contains(idx))
1173            .map(|(i, _)| i)
1174            .collect();
1175        let mut ordered = internal;
1176        ordered.sort_by_key(|&i| self.nodes[i].height);
1177        let mut accepted = 0usize;
1178        for idx in ordered {
1179            if !self.nodes[idx].is_leaf() && self.try_rotate(idx) {
1180                accepted += 1;
1181            }
1182        }
1183        accepted
1184    }
1185    /// Frustum-culling traversal: return data of all leaves whose fat AABB is
1186    /// potentially inside `frustum`.
1187    ///
1188    /// This is a named alias for `frustum_query` following the algorithm
1189    /// expansion spec.  Uses a stack-based traversal that prunes fully-outside
1190    /// subtrees for O(k + log n) complexity where k is the number of hits.
1191    pub fn traverse_frustum(&self, frustum: &BvhFrustum) -> Vec<u32> {
1192        self.frustum_query(frustum)
1193    }
1194}
1195impl DynamicBvh {
1196    /// Return the depth of a given node in the tree (root = 0).
1197    ///
1198    /// Returns `None` if `idx` is on the free list.
1199    pub fn node_depth(&self, idx: NodeIdx) -> Option<usize> {
1200        if self.free_list.contains(&idx) {
1201            return None;
1202        }
1203        let mut depth = 0usize;
1204        let mut cur = self.nodes[idx].parent;
1205        while let Some(p) = cur {
1206            depth += 1;
1207            cur = self.nodes[p].parent;
1208        }
1209        Some(depth)
1210    }
1211    /// Return the leaf node index whose `data` matches `target`, or `None`.
1212    pub fn find_leaf(&self, target: u32) -> Option<NodeIdx> {
1213        let root = self.root?;
1214        self.find_leaf_recursive(root, target)
1215    }
1216    fn find_leaf_recursive(&self, idx: NodeIdx, target: u32) -> Option<NodeIdx> {
1217        let node = &self.nodes[idx];
1218        if node.is_leaf() {
1219            return if node.data == Some(target) {
1220                Some(idx)
1221            } else {
1222                None
1223            };
1224        }
1225        if let Some(l) = node.left
1226            && let Some(found) = self.find_leaf_recursive(l, target)
1227        {
1228            return Some(found);
1229        }
1230        if let Some(r) = node.right
1231            && let Some(found) = self.find_leaf_recursive(r, target)
1232        {
1233            return Some(found);
1234        }
1235        None
1236    }
1237    /// Collect all leaf data in DFS pre-order (left-first).
1238    pub fn leaf_data_preorder(&self) -> Vec<u32> {
1239        let Some(root) = self.root else {
1240            return Vec::new();
1241        };
1242        let mut result = Vec::new();
1243        self.leaf_data_preorder_recursive(root, &mut result);
1244        result
1245    }
1246    fn leaf_data_preorder_recursive(&self, idx: NodeIdx, out: &mut Vec<u32>) {
1247        let node = &self.nodes[idx];
1248        if node.is_leaf() {
1249            if let Some(d) = node.data {
1250                out.push(d);
1251            }
1252            return;
1253        }
1254        if let Some(l) = node.left {
1255            self.leaf_data_preorder_recursive(l, out);
1256        }
1257        if let Some(r) = node.right {
1258            self.leaf_data_preorder_recursive(r, out);
1259        }
1260    }
1261    /// Return the AABB of the root node, or `None` if the tree is empty.
1262    pub fn root_aabb(&self) -> Option<BvhAabb> {
1263        self.root.map(|r| self.nodes[r].aabb)
1264    }
1265    /// Compute the maximum tree depth by traversal.
1266    pub fn max_depth(&self) -> usize {
1267        let Some(root) = self.root else {
1268            return 0;
1269        };
1270        let mut max_d = 0usize;
1271        let mut stack: Vec<(NodeIdx, usize)> = vec![(root, 0)];
1272        while let Some((idx, d)) = stack.pop() {
1273            if d > max_d {
1274                max_d = d;
1275            }
1276            let node = &self.nodes[idx];
1277            if !node.is_leaf() {
1278                if let Some(l) = node.left {
1279                    stack.push((l, d + 1));
1280                }
1281                if let Some(r) = node.right {
1282                    stack.push((r, d + 1));
1283                }
1284            }
1285        }
1286        max_d
1287    }
1288    /// Return the number of internal nodes (non-leaf, non-free).
1289    pub fn n_internal(&self) -> usize {
1290        self.nodes
1291            .iter()
1292            .enumerate()
1293            .filter(|(idx, n)| !n.is_leaf() && !self.free_list.contains(idx))
1294            .count()
1295    }
1296}
1297impl DynamicBvh {
1298    /// Return data of all leaves whose fat AABB overlaps a sphere defined by
1299    /// `center` and `radius`.
1300    ///
1301    /// Uses the AABB-vs-sphere test: the sphere overlaps an AABB iff the
1302    /// squared distance from `center` to the nearest point on the AABB is ≤ `radius²`.
1303    pub fn query_sphere(&self, center: Vec3, radius: f64) -> Vec<u32> {
1304        let radius_sq = radius * radius;
1305        let mut results = Vec::new();
1306        if let Some(root) = self.root {
1307            self.query_sphere_recursive(root, center, radius_sq, &mut results);
1308        }
1309        results
1310    }
1311    fn query_sphere_recursive(
1312        &self,
1313        idx: NodeIdx,
1314        center: Vec3,
1315        radius_sq: f64,
1316        out: &mut Vec<u32>,
1317    ) {
1318        let node = &self.nodes[idx];
1319        if node.aabb.point_dist_sq(center) > radius_sq {
1320            return;
1321        }
1322        if node.is_leaf() {
1323            if let Some(d) = node.data {
1324                out.push(d);
1325            }
1326        } else {
1327            if let Some(l) = node.left {
1328                self.query_sphere_recursive(l, center, radius_sq, out);
1329            }
1330            if let Some(r) = node.right {
1331                self.query_sphere_recursive(r, center, radius_sq, out);
1332            }
1333        }
1334    }
1335    /// Return `true` if any leaf's fat AABB overlaps the given sphere.
1336    pub fn any_in_sphere(&self, center: Vec3, radius: f64) -> bool {
1337        !self.query_sphere(center, radius).is_empty()
1338    }
1339}
1340impl DynamicBvh {
1341    /// Return data of all leaves whose fat AABB is hit by a capsule query.
1342    ///
1343    /// Implemented as AABB-vs-segment-distance test: an AABB intersects the
1344    /// capsule iff the minimum distance from the AABB to the line segment is
1345    /// ≤ `capsule.radius`.
1346    pub fn query_capsule(&self, capsule: BvhCapsule) -> Vec<u32> {
1347        let mut results = Vec::new();
1348        if let Some(root) = self.root {
1349            self.query_capsule_recursive(root, &capsule, &mut results);
1350        }
1351        results
1352    }
1353    fn query_capsule_recursive(&self, idx: NodeIdx, capsule: &BvhCapsule, out: &mut Vec<u32>) {
1354        let node = &self.nodes[idx];
1355        if self.aabb_segment_dist_sq(&node.aabb, capsule.start, capsule.end)
1356            > capsule.radius * capsule.radius
1357        {
1358            return;
1359        }
1360        if node.is_leaf() {
1361            if let Some(d) = node.data {
1362                out.push(d);
1363            }
1364        } else {
1365            if let Some(l) = node.left {
1366                self.query_capsule_recursive(l, capsule, out);
1367            }
1368            if let Some(r) = node.right {
1369                self.query_capsule_recursive(r, capsule, out);
1370            }
1371        }
1372    }
1373    /// Squared distance from the closest point on the AABB to the segment `\[p, q\]`.
1374    fn aabb_segment_dist_sq(&self, aabb: &BvhAabb, p: Vec3, q: Vec3) -> f64 {
1375        let d = q - p;
1376        let len_sq = d.x * d.x + d.y * d.y + d.z * d.z;
1377        if len_sq < 1e-300 {
1378            return aabb.point_dist_sq(p);
1379        }
1380        let c = aabb.center();
1381        let t = ((c.x - p.x) * d.x + (c.y - p.y) * d.y + (c.z - p.z) * d.z) / len_sq;
1382        let t = t.clamp(0.0, 1.0);
1383        let closest = Vec3::new(p.x + t * d.x, p.y + t * d.y, p.z + t * d.z);
1384        aabb.point_dist_sq(closest)
1385    }
1386}
1387impl DynamicBvh {
1388    /// Rebuild the tree from scratch using the current fat AABBs of all leaves.
1389    ///
1390    /// Removes all nodes, collects leaf data and AABBs, then re-inserts them.
1391    /// This is a full O(n log n) rebuild; use only when the tree is severely
1392    /// degraded (e.g. after many updates without rotations).
1393    pub fn rebuild(&mut self) {
1394        let leaves: Vec<(BvhAabb, u32)> = self
1395            .nodes
1396            .iter()
1397            .enumerate()
1398            .filter(|(idx, n)| n.is_leaf() && !self.free_list.contains(idx))
1399            .filter_map(|(_, n)| n.data.map(|d| (n.aabb, d)))
1400            .collect();
1401        self.clear();
1402        for (aabb, data) in leaves {
1403            let tight = BvhAabb {
1404                min: Vec3::new(
1405                    aabb.min.x + FAT_MARGIN,
1406                    aabb.min.y + FAT_MARGIN,
1407                    aabb.min.z + FAT_MARGIN,
1408                ),
1409                max: Vec3::new(
1410                    aabb.max.x - FAT_MARGIN,
1411                    aabb.max.y - FAT_MARGIN,
1412                    aabb.max.z - FAT_MARGIN,
1413                ),
1414            };
1415            self.insert(tight, data);
1416        }
1417    }
1418    /// Return a snapshot of all leaf `(data, fat_aabb)` pairs without
1419    /// modifying the tree.
1420    pub fn leaf_snapshot(&self) -> Vec<(u32, BvhAabb)> {
1421        self.nodes
1422            .iter()
1423            .enumerate()
1424            .filter(|(idx, n)| n.is_leaf() && !self.free_list.contains(idx))
1425            .filter_map(|(_, n)| n.data.map(|d| (d, n.aabb)))
1426            .collect()
1427    }
1428    /// Return the fat AABB for a given leaf `idx`, or `None` if it is not
1429    /// a valid leaf.
1430    pub fn leaf_aabb(&self, idx: NodeIdx) -> Option<BvhAabb> {
1431        if idx < self.nodes.len() && self.nodes[idx].is_leaf() && !self.free_list.contains(&idx) {
1432            Some(self.nodes[idx].aabb)
1433        } else {
1434            None
1435        }
1436    }
1437    /// Remove all leaves whose data value satisfies `pred`.
1438    ///
1439    /// Returns the number of leaves removed.
1440    pub fn remove_where(&mut self, pred: impl Fn(u32) -> bool) -> usize {
1441        let to_remove: Vec<NodeIdx> = self
1442            .nodes
1443            .iter()
1444            .enumerate()
1445            .filter(|(idx, n)| {
1446                n.is_leaf() && !self.free_list.contains(idx) && n.data.map(&pred).unwrap_or(false)
1447            })
1448            .map(|(i, _)| i)
1449            .collect();
1450        let count = to_remove.len();
1451        for idx in to_remove {
1452            self.remove(idx);
1453        }
1454        count
1455    }
1456    /// Replace the data value of a leaf without changing its AABB.
1457    ///
1458    /// Returns `true` if the leaf was found and updated.
1459    pub fn relabel_leaf(&mut self, leaf: NodeIdx, new_data: u32) -> bool {
1460        if leaf < self.nodes.len() && self.nodes[leaf].is_leaf() && !self.free_list.contains(&leaf)
1461        {
1462            self.nodes[leaf].data = Some(new_data);
1463            true
1464        } else {
1465            false
1466        }
1467    }
1468}
1469impl DynamicBvh {
1470    /// Return the number of leaves in the subtree rooted at `idx`.
1471    pub fn subtree_leaf_count(&self, idx: NodeIdx) -> usize {
1472        let node = &self.nodes[idx];
1473        if node.is_leaf() {
1474            return 1;
1475        }
1476        let left_count = node.left.map_or(0, |l| self.subtree_leaf_count(l));
1477        let right_count = node.right.map_or(0, |r| self.subtree_leaf_count(r));
1478        left_count + right_count
1479    }
1480    /// Return `true` if the heights stored in nodes are consistent with the
1481    /// actual tree structure.
1482    pub fn validate_heights(&self) -> bool {
1483        let Some(root) = self.root else {
1484            return true;
1485        };
1486        self.validate_heights_recursive(root)
1487    }
1488    fn validate_heights_recursive(&self, idx: NodeIdx) -> bool {
1489        let node = &self.nodes[idx];
1490        if node.is_leaf() {
1491            return node.height == 0;
1492        }
1493        let left = match node.left {
1494            Some(l) => l,
1495            None => return false,
1496        };
1497        let right = match node.right {
1498            Some(r) => r,
1499            None => return false,
1500        };
1501        let expected = 1 + self.nodes[left].height.max(self.nodes[right].height);
1502        if node.height != expected {
1503            return false;
1504        }
1505        self.validate_heights_recursive(left) && self.validate_heights_recursive(right)
1506    }
1507}
1508/// Quality metrics for the BVH tree.
1509#[derive(Debug, Clone)]
1510pub struct BvhQuality {
1511    /// SAH cost of the tree: sum of (SA(node) / SA(root)) for all internal nodes.
1512    pub sah_cost: f64,
1513    /// Maximum depth (root = depth 0).
1514    pub max_depth: usize,
1515    /// Minimum depth of any leaf.
1516    pub min_leaf_depth: usize,
1517    /// Average depth of leaves.
1518    pub avg_leaf_depth: f64,
1519    /// Number of leaves.
1520    pub leaf_count: usize,
1521    /// Number of internal nodes.
1522    pub internal_count: usize,
1523}