parry3d/partitioning/bvh/
bvh_traverse.rs

1use super::BvhNode;
2use crate::math::Real;
3use crate::partitioning::Bvh;
4use smallvec::SmallVec;
5
6const TRAVERSAL_STACK_SIZE: usize = 32;
7
8pub struct Leaves<'a, Check: Fn(&BvhNode) -> bool> {
9    tree: &'a Bvh,
10    next: Option<&'a BvhNode>,
11    stack: SmallVec<[&'a BvhNode; TRAVERSAL_STACK_SIZE]>,
12    check: Check,
13}
14
15impl<'a, Check: Fn(&BvhNode) -> bool> Leaves<'a, Check> {
16    pub fn new(tree: &'a Bvh, check: Check) -> Leaves<'a, Check> {
17        let mut stack = SmallVec::default();
18        let mut next = None;
19
20        if let Some(root) = tree.nodes.first() {
21            if check(&root.left) {
22                next = Some(&root.left);
23            }
24
25            if root.right.leaf_count() > 0 && check(&root.right) {
26                stack.push(&root.right);
27            }
28        }
29
30        Leaves {
31            tree,
32            next,
33            stack,
34            check,
35        }
36    }
37}
38
39impl<'a, Check: Fn(&BvhNode) -> bool> Iterator for Leaves<'a, Check> {
40    type Item = u32;
41    fn next(&mut self) -> Option<Self::Item> {
42        loop {
43            if self.next.is_none() {
44                self.next = self.stack.pop();
45            }
46
47            let node = self.next.take()?;
48
49            if node.is_leaf() {
50                return Some(node.children);
51            }
52
53            let children = &self.tree.nodes[node.children as usize];
54            let left = &children.left;
55            let right = &children.right;
56
57            if (self.check)(left) {
58                self.next = Some(left);
59            }
60
61            if (self.check)(right) {
62                if self.next.is_none() {
63                    self.next = Some(right);
64                } else {
65                    self.stack.push(right);
66                }
67            }
68        }
69    }
70}
71
72/// Cost associated to a BVH leaf during best-first traversal.
73pub trait BvhLeafCost {
74    /// The cost value associated to the leaf.
75    ///
76    /// Best-first searches for the leaf with the lowest cost.
77    fn cost(&self) -> Real;
78}
79
80impl BvhLeafCost for Real {
81    #[inline(always)]
82    fn cost(&self) -> Real {
83        *self
84    }
85}
86
87impl<T> BvhLeafCost for (Real, T) {
88    #[inline(always)]
89    fn cost(&self) -> Real {
90        self.0
91    }
92}
93
94impl Bvh {
95    /// Iterates through the leaves, in depth-first order.
96    ///
97    /// The `check_node` closure is called on every traversed node. If it returns `false` then the
98    /// node and all its descendants won’t be iterated on. This is useful for pruning whole
99    /// sub-trees based on a geometric predicate on the node’s AABB.
100    ///
101    /// See also the [`Bvh::traverse`] function which is slightly less convenient since it doesn’t
102    /// rely on the iterator system, but takes a closure that implements [`FnMut`] instead of [`Fn`].
103    pub fn leaves<F: Fn(&BvhNode) -> bool>(&self, check_node: F) -> Leaves<'_, F> {
104        Leaves::new(self, check_node)
105    }
106}
107
108/// Controls the execution flow of [`Bvh::traverse`].
109pub enum TraversalAction {
110    /// The traversal will continue on the children of the tested node.
111    Continue,
112    /// The traversal will skip all descendants of the tested node.
113    Prune,
114    /// The traversal will exit immediately.
115    EarlyExit,
116}
117
118impl Bvh {
119    #[inline(always)]
120    pub(crate) fn traversal_stack() -> SmallVec<[u32; 32]> {
121        Default::default()
122    }
123
124    /// Traverses the BVH in depth-first order with full control over traversal.
125    ///
126    /// This method provides low-level control over tree traversal. For each node visited,
127    /// it calls your closure which can decide whether to continue traversing, prune that
128    /// subtree, or exit early.
129    ///
130    /// # Arguments
131    ///
132    /// * `check_node` - A mutable closure called for each node. It receives a [`BvhNode`]
133    ///   and returns a [`TraversalAction`] to control the traversal:
134    ///   - `Continue`: Visit this node's children (if not a leaf)
135    ///   - `Prune`: Skip this node's subtree entirely
136    ///   - `EarlyExit`: Stop traversal immediately
137    ///
138    /// # Traversal Order
139    ///
140    /// The tree is traversed in **depth-first** order:
141    /// 1. Check current node with `check_node`
142    /// 2. If `Continue`, descend into left child first
143    /// 3. Then descend into right child
144    /// 4. Backtrack when both subtrees are processed
145    ///
146    /// This order is cache-friendly and matches the tree's memory layout.
147    ///
148    /// # When to Use
149    ///
150    /// Use `traverse` when you need:
151    /// - **Mutable state** during traversal
152    /// - **Early exit** conditions
153    /// - **Full control** over pruning decisions
154    /// - **Custom query logic** that doesn't fit standard patterns
155    ///
156    /// For simpler cases, consider:
157    /// - [`leaves`](Self::leaves) - Iterator over leaves matching a predicate
158    /// - [`intersect_aabb`](Self::intersect_aabb) - Find leaves intersecting an AABB
159    /// - [`cast_ray`](Self::cast_ray) - Ray casting queries
160    ///
161    /// # Performance
162    ///
163    /// - **Best case**: O(log n) when pruning effectively
164    /// - **Worst case**: O(n) when visiting all nodes
165    /// - Cache-friendly due to depth-first order
166    /// - No allocations beyond a small stack (~32 entries)
167    ///
168    /// # Examples
169    ///
170    /// ## Count leaves in a region
171    ///
172    /// ```
173    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
174    /// use parry3d::partitioning::{Bvh, BvhBuildStrategy, TraversalAction};
175    /// use parry3d::bounding_volume::{Aabb, BoundingVolume};
176    /// use nalgebra::Point3;
177    ///
178    /// let aabbs = vec![
179    ///     Aabb::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0)),
180    ///     Aabb::new(Point3::new(5.0, 0.0, 0.0), Point3::new(6.0, 1.0, 1.0)),
181    ///     Aabb::new(Point3::new(10.0, 0.0, 0.0), Point3::new(11.0, 1.0, 1.0)),
182    /// ];
183    ///
184    /// let bvh = Bvh::from_leaves(BvhBuildStrategy::default(), &aabbs);
185    ///
186    /// let query_region = Aabb::new(
187    ///     Point3::new(-1.0, -1.0, -1.0),
188    ///     Point3::new(7.0, 2.0, 2.0)
189    /// );
190    ///
191    /// let mut count = 0;
192    /// bvh.traverse(|node| {
193    ///     if !node.aabb().intersects(&query_region) {
194    ///         // Prune: this entire subtree is outside the region
195    ///         return TraversalAction::Prune;
196    ///     }
197    ///
198    ///     if node.is_leaf() {
199    ///         count += 1;
200    ///     }
201    ///
202    ///     TraversalAction::Continue
203    /// });
204    ///
205    /// assert_eq!(count, 2); // First two objects intersect the region
206    /// # }
207    /// ```
208    ///
209    /// ## Find first leaf matching a condition
210    ///
211    /// ```
212    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
213    /// use parry3d::partitioning::{Bvh, BvhBuildStrategy, TraversalAction};
214    /// use parry3d::bounding_volume::Aabb;
215    /// use nalgebra::Point3;
216    ///
217    /// let aabbs = vec![
218    ///     Aabb::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0)),
219    ///     Aabb::new(Point3::new(5.0, 0.0, 0.0), Point3::new(6.0, 1.0, 1.0)),
220    ///     Aabb::new(Point3::new(10.0, 0.0, 0.0), Point3::new(11.0, 1.0, 1.0)),
221    /// ];
222    ///
223    /// let bvh = Bvh::from_leaves(BvhBuildStrategy::default(), &aabbs);
224    ///
225    /// let target_point = Point3::new(5.5, 0.5, 0.5);
226    /// let mut found_leaf = None;
227    ///
228    /// bvh.traverse(|node| {
229    ///     if !node.aabb().contains_local_point(&target_point) {
230    ///         return TraversalAction::Prune;
231    ///     }
232    ///
233    ///     if let Some(leaf_id) = node.leaf_data() {
234    ///         found_leaf = Some(leaf_id);
235    ///         return TraversalAction::EarlyExit; // Found it, stop searching
236    ///     }
237    ///
238    ///     TraversalAction::Continue
239    /// });
240    ///
241    /// assert_eq!(found_leaf, Some(1));
242    /// # }
243    /// ```
244    ///
245    /// ## Collect statistics with mutable state
246    ///
247    /// ```
248    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
249    /// use parry3d::partitioning::{Bvh, BvhBuildStrategy, TraversalAction};
250    /// use parry3d::bounding_volume::Aabb;
251    /// use nalgebra::Point3;
252    ///
253    /// let aabbs = vec![
254    ///     Aabb::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0)),
255    ///     Aabb::new(Point3::new(2.0, 0.0, 0.0), Point3::new(3.0, 1.0, 1.0)),
256    ///     Aabb::new(Point3::new(4.0, 0.0, 0.0), Point3::new(5.0, 1.0, 1.0)),
257    /// ];
258    ///
259    /// let bvh = Bvh::from_leaves(BvhBuildStrategy::default(), &aabbs);
260    ///
261    /// let mut stats = (0, 0); // (num_internal_nodes, num_leaves)
262    ///
263    /// bvh.traverse(|node| {
264    ///     if node.is_leaf() {
265    ///         stats.1 += 1;
266    ///     } else {
267    ///         stats.0 += 1;
268    ///     }
269    ///     TraversalAction::Continue
270    /// });
271    ///
272    /// assert_eq!(stats.1, 3); // 3 leaves
273    /// # }
274    /// ```
275    ///
276    /// # Notes
277    ///
278    /// - The closure is called for **every** visited node (internal and leaf)
279    /// - Pruning a node skips all its descendants
280    /// - Use `node.is_leaf()` to distinguish leaves from internal nodes
281    /// - Use `node.leaf_data()` to get the leaf's index (returns `None` for internal nodes)
282    /// - The traversal uses a small fixed-size stack internally
283    ///
284    /// # See Also
285    ///
286    /// - [`leaves`](Self::leaves) - Simpler iterator interface for leaf traversal
287    /// - [`intersect_aabb`](Self::intersect_aabb) - Specialized AABB intersection query
288    /// - [`cast_ray`](Self::cast_ray) - Ray casting with best-first traversal
289    /// - [`TraversalAction`] - Controls traversal flow
290    pub fn traverse(&self, mut check_node: impl FnMut(&BvhNode) -> TraversalAction) {
291        let mut stack = Self::traversal_stack();
292        let mut curr_id = 0;
293
294        if self.nodes.is_empty() {
295            return;
296        } else if self.nodes[0].right.leaf_count() == 0 {
297            // Special case for partial root.
298            let _ = check_node(&self.nodes[0].left);
299            return;
300        }
301
302        loop {
303            let node = &self.nodes[curr_id as usize];
304            let left = &node.left;
305            let right = &node.right;
306            let go_left = match check_node(left) {
307                TraversalAction::Continue => !left.is_leaf(),
308                TraversalAction::Prune => false,
309                TraversalAction::EarlyExit => return,
310            };
311            let go_right = match check_node(right) {
312                TraversalAction::Continue => !right.is_leaf(),
313                TraversalAction::Prune => false,
314                TraversalAction::EarlyExit => return,
315            };
316
317            match (go_left, go_right) {
318                (true, true) => {
319                    curr_id = left.children;
320                    stack.push(right.children);
321                }
322                (true, false) => curr_id = left.children,
323                (false, true) => curr_id = right.children,
324                (false, false) => {
325                    let Some(next) = stack.pop() else {
326                        return;
327                    };
328                    curr_id = next;
329                }
330            }
331        }
332    }
333
334    /// Find the leaf that minimizes their associated cost.
335    pub fn find_best<L: BvhLeafCost>(
336        &self,
337        max_cost: Real,
338        aabb_cost: impl Fn(&BvhNode, Real) -> Real,
339        leaf_cost: impl Fn(u32, Real) -> Option<L>,
340    ) -> Option<(u32, L)> {
341        // A stack with 32 elements should be more than enough in most cases.
342        let mut stack = Self::traversal_stack();
343        let mut best_val = None;
344        let mut best_cost = max_cost;
345        let mut best_id = u32::MAX;
346        let mut curr_id = 0;
347
348        if self.nodes.is_empty() {
349            return None;
350        } else if self.nodes[0].right.leaf_count() == 0 {
351            // Special case for partial root.
352            let leaf = &self.nodes[0].left;
353            if aabb_cost(leaf, max_cost) < max_cost {
354                let cost = leaf_cost(leaf.children, best_cost)?;
355                return (cost.cost() < max_cost).then_some((leaf.children, cost));
356            } else {
357                return None;
358            }
359        }
360
361        loop {
362            let node = &self.nodes[curr_id as usize];
363            let mut left = &node.left;
364            let mut right = &node.right;
365
366            let mut left_score = aabb_cost(left, best_cost);
367            let mut right_score = aabb_cost(right, best_cost);
368
369            if left_score > right_score {
370                core::mem::swap(&mut left_score, &mut right_score);
371                core::mem::swap(&mut left, &mut right);
372            }
373
374            let mut found_next = false;
375            if left_score < best_cost && left_score != Real::MAX {
376                if left.is_leaf() {
377                    if let Some(primitive_val) = leaf_cost(left.children, best_cost) {
378                        let primitive_score = primitive_val.cost();
379                        if primitive_score < best_cost {
380                            best_val = Some(primitive_val);
381                            best_cost = primitive_score;
382                            best_id = left.children;
383                        }
384                    }
385                } else {
386                    curr_id = left.children;
387                    found_next = true;
388                }
389            }
390
391            if right_score < best_cost && right_score != Real::MAX {
392                if right.is_leaf() {
393                    if let Some(primitive_val) = leaf_cost(right.children, best_cost) {
394                        let primitive_score = primitive_val.cost();
395                        if primitive_score < best_cost {
396                            best_val = Some(primitive_val);
397                            best_cost = primitive_score;
398                            best_id = right.children;
399                        }
400                    }
401                } else if found_next {
402                    stack.push(right.children);
403                } else {
404                    curr_id = right.children;
405                    found_next = true;
406                }
407            }
408
409            if !found_next {
410                if let Some(next) = stack.pop() {
411                    curr_id = next;
412                } else {
413                    return best_val.map(|val| (best_id, val));
414                }
415            }
416        }
417    }
418}