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}