obvhs/bvh2/insertion_removal.rs
1use core::f32;
2
3use crate::{
4 Boundable, INVALID,
5 aabb::Aabb,
6 bvh2::{Bvh2, Bvh2Node, update_primitives_to_nodes_for_node},
7 faststack::{FastStack, HeapStack},
8};
9
10use super::DEFAULT_MAX_STACK_DEPTH;
11
12// The index and inherited_cost of a given candidate sibling used for insertion.
13#[doc(hidden)]
14#[derive(Debug, Default, Clone, Copy)]
15pub struct SiblingInsertionCandidate {
16 inherited_cost: f32,
17 index: u32,
18}
19
20impl Bvh2 {
21 /// Searches the tree with the greedy descent in [`Bvh2::find_sibling_greedy()`] to find a sibling
22 /// for the node being inserted, then attaches it there and refits back up to the root,
23 /// performing rotations along the way to help prevent the BVH from degenerating.
24 ///
25 /// This is the faster counterpart of [`Bvh2::insert_leaf_precise()`]. It doesn't need a traversal stack
26 /// and it tries to keep [`Bvh2::max_depth`] up to date, at the cost of sometimes picking a slightly worse sibling.
27 ///
28 /// # Returns
29 /// The index of the newly added node. Note that this is generally not `self.nodes.len() - 1`,
30 /// since a rotation on the refit walk can move the node that was just attached.
31 ///
32 /// # Arguments
33 /// * `new_node` - This node must be a leaf and already have a valid `first_index` into `primitive_indices`.
34 pub fn insert_leaf(&mut self, new_node: Bvh2Node) -> usize {
35 assert!(new_node.is_leaf());
36
37 if self.nodes.is_empty() {
38 self.nodes.push(new_node);
39 self.parents.clear();
40 self.parents.push(0);
41 return 0;
42 }
43
44 self.init_parents_if_uninit();
45
46 let (sibling_id, sibling_depth) = self.find_sibling_greedy(new_node.aabb());
47
48 // Grow by an adjacent pair at the end for the displaced sibling and the new node.
49 // The node count is always odd, so the first of the two is always a left sibling.
50 let left_id = self.nodes.len();
51 self.nodes.push(Default::default());
52 self.nodes.push(Default::default());
53 self.parents.push(0);
54 self.parents.push(0);
55
56 let new_node_id = self.attach_leaf(new_node, sibling_id, left_id);
57 self.update_max_depth_for_greedy_insertion(sibling_depth);
58
59 // Need to work up the tree updating the aabbs since we just added a node.
60 // A rotation along the way can move the node we just attached, so we track it.
61 self.refit_and_rotate_from_tracking_fast(sibling_id, new_node_id)
62 }
63
64 /// Removes and returns the leaf specified by `node_id`.
65 /// Puts `node_id` sibling in its parents place then moves the last two nodes into the now empty slots at `node_id`
66 /// and its sibling.
67 ///
68 /// Doesn't update the primitive_indices mapping. If this node is just going to be re-inserted again, nothing needs
69 /// to be done with primitive_indices, the mapping will still be valid. If this primitive needs to be removed
70 /// permanently see Bvh2::remove_primitive().
71 ///
72 /// # Arguments
73 /// * `node_id` - The index into self.nodes of the node that is to be removed
74 pub fn remove_leaf(&mut self, node_id: usize) -> Bvh2Node {
75 assert!(
76 !self.uses_spatial_splits,
77 "Removing leaves while using spatial splits is currently unsupported as it would require a mapping \
78from one primitive to multiple nodes in `Bvh2::primitives_to_nodes`."
79 );
80
81 let node_to_remove = self.nodes[node_id];
82 assert!(node_to_remove.is_leaf());
83
84 if self.nodes.len() == 1 {
85 // Special case if the BVH is just a leaf
86 self.nodes.clear();
87 self.parents.clear();
88 self.primitives_to_nodes.clear();
89 return node_to_remove;
90 }
91
92 // if primitives_to_nodes has already been initialized
93 if !self.primitives_to_nodes.is_empty() {
94 // Invalidate primitives_to_nodes instances
95 for node_prim_id in
96 node_to_remove.first_index..node_to_remove.first_index + node_to_remove.prim_count
97 {
98 let direct_prim_id = self.primitive_indices[node_prim_id as usize];
99 self.primitives_to_nodes[direct_prim_id as usize] = INVALID;
100 }
101 }
102
103 let sibling_id = Bvh2Node::get_sibling_id(node_id);
104 debug_assert_eq!(self.parents[node_id], self.parents[sibling_id]); // Both children should already have the same parent.
105 let mut parent_id = self.parents[node_id] as usize;
106
107 // Put sibling in parent's place (parent doesn't exist anymore)
108 let sibling = self.nodes[sibling_id];
109 self.nodes[parent_id] = sibling;
110 // Tell the children (or primitives) of the moved sibling where it went.
111 self.relink_node(&sibling, parent_id);
112 // Don't need to update other parents here since the parent that was for this `parent_id` slot is now the direct
113 // parent of the moved sibling, and the parents of `node_id` and `sibling_id` are updated below.
114
115 // Now slots at both node_id and sibling_id are empty.
116 // Take the last two nodes "src" and put them in those now empty "dst" slots.
117 let end_nodes = node_id >= self.nodes.len() - 2;
118 if end_nodes {
119 // If these were already the last 2 nodes in the list we can just discard both.
120 self.nodes.pop().unwrap();
121 self.nodes.pop().unwrap();
122 self.parents.pop().unwrap();
123 self.parents.pop().unwrap();
124 } else {
125 let dst_left_id = Bvh2Node::get_left_sibling_id(node_id);
126 let dst_right_id = Bvh2Node::get_right_sibling_id(node_id);
127
128 let src_left_id = self.nodes.len() as u32 - 2;
129 let src_right_id = Bvh2Node::get_sibling_id32(src_left_id);
130 let src_right_parent = self.parents.pop().unwrap();
131 let src_left_parent = self.parents.pop().unwrap();
132
133 self.parents[dst_left_id] = src_left_parent;
134 self.parents[dst_right_id] = src_right_parent;
135
136 debug_assert_eq!(src_left_parent, src_right_parent); // Both children should already have the same parent.
137 let parent_of_relocated = &mut self.nodes[src_left_parent as usize];
138 debug_assert!(!parent_of_relocated.is_leaf());
139 debug_assert_eq!(parent_of_relocated.first_index, src_left_id);
140 debug_assert_eq!(parent_of_relocated.first_index + 1, src_right_id);
141 // Tell the actual parent of the nodes that are moving where they're going to be now.
142 self.nodes[src_left_parent as usize].first_index = dst_left_id as u32;
143
144 let right_src_sibling = self.nodes.pop().unwrap(); // Last node is right src sibling
145 self.nodes[dst_right_id] = right_src_sibling;
146 // Tell the children (or primitives) of the moved right_src_sibling where it went.
147 self.relink_node(&right_src_sibling, dst_right_id);
148
149 let left_src_sibling = self.nodes.pop().unwrap(); // Last node is left src sibling
150 self.nodes[dst_left_id] = left_src_sibling;
151 // Tell the children (or primitives) of the moved left_src_sibling where it went.
152 self.relink_node(&left_src_sibling, dst_left_id);
153
154 // If the to be removed node's parent was at the end of the array and has now moved update parent_id:
155 if parent_id as u32 == src_left_id {
156 parent_id = dst_left_id;
157 }
158 if parent_id as u32 == src_right_id {
159 parent_id = dst_right_id;
160 }
161 }
162
163 // Need to work up the tree updating the aabbs since we just removed a node.
164 self.refit_from_fast(parent_id);
165
166 self.children_are_ordered_after_parents = false;
167 // Return the removed node.
168 node_to_remove
169 }
170
171 /// Searches the tree with the greedy descent in [`Bvh2::find_sibling_greedy()`] to find a sibling
172 /// for the primitive being inserted, then attaches it there and refits back up to the root,
173 /// performing rotations along the way to help prevent the BVH from degenerating.
174 /// Updates [`Bvh2::primitive_indices`] and [`Bvh2::primitive_indices_freelist`].
175 ///
176 /// This is the faster counterpart of [`Bvh2::insert_primitive_precise()`]. It doesn't need a traversal stack
177 /// and it tries to keep [`Bvh2::max_depth`] up to date, at the cost of sometimes picking a slightly worse sibling.
178 ///
179 /// # Returns
180 /// The index of the newly added node.
181 ///
182 /// # Arguments
183 /// * `aabb` - The aabb of the primitive being inserted.
184 /// * `primitive_id` - The index of the primitive being inserted.
185 pub fn insert_primitive(&mut self, aabb: Aabb, primitive_id: u32) -> usize {
186 self.init_primitives_to_nodes_if_uninit();
187 self.init_parents_if_uninit();
188 if self.primitives_to_nodes.len() <= primitive_id as usize {
189 self.primitives_to_nodes
190 .resize(primitive_id as usize + 1, INVALID);
191 }
192 let first_index = if let Some(free_slot) = self.primitive_indices_freelist.pop() {
193 self.primitive_indices[free_slot as usize] = primitive_id;
194 free_slot
195 } else {
196 self.primitive_indices.push(primitive_id);
197 self.primitive_indices.len() as u32 - 1
198 };
199 let new_node_id = self.insert_leaf(Bvh2Node::new(aabb, 1, first_index));
200 self.primitives_to_nodes[primitive_id as usize] = new_node_id as u32;
201 new_node_id
202 }
203
204 /// Removes the leaf that contains the given primitive. Should be correct for nodes with multiple primitives per
205 /// leaf but faster for nodes with only one primitive per leaf, and will leave node aabb oversized.
206 /// Updates Bvh2::primitive_indices and Bvh2::primitive_indices_freelist.
207 ///
208 /// # Arguments
209 /// * `primitive_id` - The index of the primitive being removed.
210 pub fn remove_primitive(&mut self, primitive_id: u32) {
211 assert!(
212 !self.uses_spatial_splits,
213 "Removing primitives while using spatial splits is currently unsupported as it would require a mapping \
214from one primitive to multiple nodes in `Bvh2::primitives_to_nodes`."
215 );
216 let remove_primitive_id = primitive_id;
217 self.init_parents_if_uninit();
218 self.init_primitives_to_nodes_if_uninit();
219
220 let node_id = self.primitives_to_nodes[remove_primitive_id as usize];
221
222 let node = &self.nodes[node_id as usize];
223 assert!(node.is_leaf());
224 if node.prim_count == 1 {
225 let removed_leaf = self.remove_leaf(node_id as usize);
226 self.primitive_indices_freelist
227 .push(removed_leaf.first_index);
228 self.primitive_indices[removed_leaf.first_index as usize] = INVALID;
229 } else {
230 // Update leaf with the remaining primitives, use the existing leftover space in primitive_indices and
231 // only add the removed primitive to the freelist
232
233 let node = &mut self.nodes[node_id as usize];
234
235 let start = node.first_index as usize;
236 let end = (node.first_index + node.prim_count) as usize;
237 let last = end - 1;
238 let mut spare_spot_id = start;
239 // Condense primitive_indices for this node.
240 for node_prim_id in start..end {
241 let direct_prim_id = self.primitive_indices[node_prim_id];
242 if direct_prim_id == remove_primitive_id {
243 break;
244 }
245 spare_spot_id += 1;
246 }
247 if spare_spot_id < last {
248 self.primitive_indices[spare_spot_id] = self.primitive_indices[last];
249 }
250 // Free now open last position.
251 self.primitive_indices_freelist.push(last as u32);
252 self.primitive_indices[last] = INVALID;
253
254 assert!(node.prim_count > 1);
255 node.prim_count -= 1;
256 }
257
258 if self.primitives_to_nodes.len() > remove_primitive_id as usize {
259 self.primitives_to_nodes[remove_primitive_id as usize] = INVALID;
260 }
261 }
262
263 /// Attaches `new_node` to the BVH as the new sibling of the node at `sibling_id`.
264 ///
265 /// The new parent takes the sibling's slot, so nothing above `sibling_id` moves.
266 /// The displaced sibling and `new_node` are put in the adjacent pair of slots
267 /// at `left_id` and `left_id + 1`. Both of those slots have to already exist
268 /// and must not be reachable from the root.
269 ///
270 /// # Returns
271 /// The index of the newly attached node (always `left_id + 1`).
272 fn attach_leaf(&mut self, new_node: Bvh2Node, sibling_id: usize, left_id: usize) -> usize {
273 debug_assert!(Bvh2Node::is_left_sibling(left_id));
274
275 let new_node_id = left_id + 1;
276 let sibling = self.nodes[sibling_id];
277
278 // New parent goes in the sibling's position.
279 self.nodes[sibling_id] =
280 Bvh2Node::new(new_node.aabb().union(sibling.aabb()), 0, left_id as u32);
281 self.nodes[left_id] = sibling;
282 self.nodes[new_node_id] = new_node;
283 self.parents[left_id] = sibling_id as u32;
284 self.parents[new_node_id] = sibling_id as u32;
285
286 // Tell the children (or primitives) of the moved sibling where it went.
287 self.relink(left_id);
288
289 // if primitives_to_nodes has already been initialized
290 if !self.primitives_to_nodes.is_empty() {
291 // Tell primitives where their node went.
292 let end = new_node.first_index + new_node.prim_count;
293 if self.primitives_to_nodes.len() < end as usize {
294 // Since we are adding a primitive it's possible that primitives_to_nodes is not large enough yet.
295 self.primitives_to_nodes.resize(end as usize, INVALID);
296 }
297 update_primitives_to_nodes_for_node(
298 &new_node,
299 new_node_id,
300 &self.primitive_indices,
301 &mut self.primitives_to_nodes,
302 )
303 }
304
305 if left_id < sibling_id || (!sibling.is_leaf() && sibling.first_index as usize <= left_id) {
306 // The sibling was moved, but its children stayed where they were,
307 // so they may now come before their parent. The new pair may also
308 // come before the new parent when reusing freed slots.
309 self.children_are_ordered_after_parents = false;
310 }
311
312 new_node_id
313 }
314
315 /// Detaches the leaf at `node_id` without compacting `Bvh2::nodes`.
316 ///
317 /// The sibling takes the parent's slot, and the slots at `node_id` and its sibling then fall free.
318 /// They are unreachable from the root, so nothing can select one of them as an insertion sibling.
319 ///
320 /// Doesn't update the `primitive_indices` mapping, and leaves the `primitives_to_nodes` entries of the detached leaf
321 /// pointing at `node_id`. Both are still valid if the leaf is immediately reattached, see [`Bvh2::move_leaf()`].
322 ///
323 /// # Returns
324 /// The index of the left slot of the freed pair.
325 fn detach_leaf(&mut self, node_id: usize) -> usize {
326 let sibling_id = Bvh2Node::get_sibling_id(node_id);
327 debug_assert_eq!(
328 self.parents[node_id], self.parents[sibling_id],
329 "Both children should already have the same parent."
330 );
331 let parent_id = self.parents[node_id] as usize;
332
333 // Put sibling in parent's place (parent doesn't exist anymore)
334 self.nodes[parent_id] = self.nodes[sibling_id];
335 self.relink(parent_id);
336
337 // Need to work up the tree updating the aabbs since we just removed a node.
338 self.refit_from_fast(parent_id);
339
340 Bvh2Node::get_left_sibling_id(node_id)
341 }
342
343 /// Moves the leaf specified by `node_id` to a new position in the BVH, resizing it to `aabb`.
344 /// Refits back up to the root, performing rotations along the way to help prevent the BVH from degenerating.
345 ///
346 /// This is the fused equivalent of [`Bvh2::remove_leaf()`] followed by [`Bvh2::insert_leaf()`].
347 ///
348 /// `Bvh2::nodes.len()` is unchanged across the call and `Bvh2::primitive_indices` is untouched.
349 ///
350 /// # Returns
351 /// The index of the moved node.
352 ///
353 /// # Arguments
354 /// * `node_id` - The index into `self.nodes` of the leaf being moved.
355 /// * `aabb` - The new aabb of the leaf.
356 pub fn move_leaf(&mut self, node_id: usize, aabb: Aabb) -> usize {
357 assert!(
358 !self.uses_spatial_splits,
359 "Moving leaves while using spatial splits is currently unsupported as it would require a mapping \
360from one primitive to multiple nodes in `Bvh2::primitives_to_nodes`."
361 );
362
363 let mut node = self.nodes[node_id];
364 assert!(node.is_leaf());
365 node.set_aabb(aabb);
366
367 if self.nodes.len() == 1 {
368 self.nodes[0] = node;
369 return 0;
370 }
371 debug_assert_ne!(node_id, 0);
372
373 self.init_parents_if_uninit();
374
375 let left_id = self.detach_leaf(node_id);
376 let (sibling_id, sibling_depth) = self.find_sibling_greedy(node.aabb());
377 let new_node_id = self.attach_leaf(node, sibling_id, left_id);
378 self.update_max_depth_for_greedy_insertion(sibling_depth);
379
380 // Need to work up the tree updating the aabbs since we just attached a node.
381 // A rotation along the way can move the node we just attached, so we track it.
382 self.refit_and_rotate_from_tracking_fast(sibling_id, new_node_id)
383 }
384
385 /// Moves the leaf that contains the given primitive to a new position in the BVH, resizing it to `aabb`.
386 /// Refits back up to the root, performing rotations along the way to help prevent the BVH from degenerating.
387 ///
388 /// This is the fused equivalent of [`Bvh2::remove_primitive()`] followed by [`Bvh2::insert_primitive()`].
389 /// The whole leaf is moved, so this requires that the leaf contains only this primitive.
390 ///
391 /// # Returns
392 /// The new index of the node of this primitive.
393 ///
394 /// # Arguments
395 /// * `aabb` - The new aabb of the primitive.
396 /// * `primitive_id` - The index of the primitive being moved.
397 pub fn move_primitive(&mut self, aabb: Aabb, primitive_id: u32) -> usize {
398 self.init_primitives_to_nodes_if_uninit();
399 self.init_parents_if_uninit();
400 let node_id = self.primitives_to_nodes[primitive_id as usize] as usize;
401 assert_eq!(
402 self.nodes[node_id].prim_count, 1,
403 "Bvh2::move_primitive() would move the other primitives in this leaf along with it."
404 );
405 let new_node_id = self.move_leaf(node_id, aabb);
406 debug_assert_eq!(
407 self.primitives_to_nodes[primitive_id as usize],
408 new_node_id as u32
409 );
410 new_node_id
411 }
412
413 /// Searches the tree recursively to find the best sibling for the primitive being inserted
414 /// (see [`Bvh2::insert_leaf_precise()`]). Updates [`Bvh2::primitive_indices`] and
415 /// [`Bvh2::primitive_indices_freelist`]. Refits back up to the root, performing rotations
416 /// along the way to help prevent the BVH from degenerating.
417 ///
418 /// See [`Bvh2::insert_primitive()`] for a faster version that descends a single path when choosing the sibling.
419 /// Unlike this method, [`Bvh2::insert_primitive()`] also tries to keep [`Bvh2::max_depth`] up to date.
420 ///
421 /// # Returns
422 /// The index of the newly added node. Note that this is generally not `self.nodes.len() - 1`,
423 /// since a rotation on the refit walk can move the node that was just attached.
424 ///
425 /// # Arguments
426 /// * `aabb` - The aabb of the primitive being inserted.
427 /// * `primitive_id` - The index of the primitive being inserted.
428 /// * `stack` - Used for the traversal stack. Needs to be large enough to initially accommodate traversal to the
429 /// deepest leaf of the BVH. `insert_leaf_precise()` will resize this stack after traversal to be at least 2x the
430 /// required size. This ends up being quite a bit faster than using a Vec and works well when inserting multiple
431 /// nodes. But does require the user to provide a good initial guess. SiblingInsertionCandidate is tiny so be
432 /// generous. Something like: `stack.reserve(bvh.depth(0) * 2).max(1000);` If you are inserting a lot of leaves
433 /// don't call `bvh.depth(0)` with each leaf; just let `insert_leaf_precise()` resize the stack as needed.
434 pub fn insert_primitive_precise(
435 &mut self,
436 aabb: Aabb,
437 primitive_id: u32,
438 stack: &mut HeapStack<SiblingInsertionCandidate>,
439 ) -> usize {
440 self.init_primitives_to_nodes_if_uninit();
441 self.init_parents_if_uninit();
442 if self.primitives_to_nodes.len() <= primitive_id as usize {
443 self.primitives_to_nodes
444 .resize(primitive_id as usize + 1, INVALID);
445 }
446 let first_index = if let Some(free_slot) = self.primitive_indices_freelist.pop() {
447 self.primitive_indices[free_slot as usize] = primitive_id;
448 free_slot
449 } else {
450 self.primitive_indices.push(primitive_id);
451 self.primitive_indices.len() as u32 - 1
452 };
453 let new_node_id = self.insert_leaf_precise(Bvh2Node::new(aabb, 1, first_index), stack);
454 self.primitives_to_nodes[primitive_id as usize] = new_node_id as u32;
455 new_node_id
456 }
457
458 /// Searches the tree recursively to find the best sibling for the node being inserted. The best sibling is
459 /// classified as the sibling that if chosen it would increase the surface area of the BVH the least.
460 /// When the best sibling is found, a parent of both the sibling and the new node is put in the location of
461 /// the sibling and both the sibling and new node are added to the end of `Bvh2::nodes`.
462 /// Refits back up to the root, performing rotations along the way to help prevent the BVH from degenerating.
463 ///
464 /// See "Branch and Bound" <https://box2d.org/files/ErinCatto_DynamicBVH_Full.pdf> and
465 /// Jiˇrí Bittner et al. 2012 Fast Insertion-Based Optimization of Bounding Volume Hierarchies
466 ///
467 /// See [`Bvh2::insert_leaf()`] for a faster version that descends a single path when choosing the sibling.
468 /// Unlike this method, [`Bvh2::insert_leaf()`] also tries to keep [`Bvh2::max_depth`] up to date.
469 ///
470 /// # Returns
471 /// The index of the newly added node. Note that this is generally not `self.nodes.len() - 1`,
472 /// since a rotation on the refit walk can move the node that was just attached.
473 ///
474 /// # Arguments
475 /// * `new_node` - This node must be a leaf and already have a valid `first_index` into `primitive_indices`.
476 /// * `stack` - Used for the traversal stack. Needs to be large enough to initially accommodate traversal to the
477 /// deepest leaf of the BVH. `insert_leaf_precise()` will resize this stack after traversal to be at least 2x the
478 /// required size. This ends up being quite a bit faster than using a Vec and works well when inserting multiple
479 /// nodes. But does require the user to provide a good initial guess. SiblingInsertionCandidate is tiny so be
480 /// generous. Something like: `stack.reserve(bvh.depth(0) * 2).max(1000);` If you are inserting a lot of leaves
481 /// don't call `bvh.depth(0)` with each leaf; just let `insert_leaf_precise()` resize the stack as needed.
482 pub fn insert_leaf_precise(
483 &mut self,
484 new_node: Bvh2Node,
485 stack: &mut HeapStack<SiblingInsertionCandidate>,
486 ) -> usize {
487 assert!(new_node.is_leaf());
488
489 if self.nodes.is_empty() {
490 self.nodes.push(new_node);
491 self.parents.clear();
492 self.parents.push(0);
493 return 0;
494 }
495
496 self.init_parents_if_uninit();
497
498 let mut min_cost = f32::MAX;
499 let mut best_sibling_candidate_id = 0;
500 let mut max_stack_len = 1;
501 let new_node_cost = new_node.aabb().half_area();
502
503 stack.clear();
504 let root_aabb = self.nodes[0].aabb();
505
506 // Traverse the BVH to find the best sibling
507 stack.push(SiblingInsertionCandidate {
508 inherited_cost: root_aabb.union(new_node.aabb()).half_area() - root_aabb.half_area(),
509 index: 0,
510 });
511 while let Some(sibling_candidate) = stack.pop() {
512 let current_node_index = sibling_candidate.index as usize;
513
514 let candidate = &self.nodes[current_node_index];
515
516 let direct_cost = candidate.aabb().union(new_node.aabb()).half_area();
517 let total_cost = direct_cost + sibling_candidate.inherited_cost;
518
519 if total_cost < min_cost {
520 min_cost = total_cost;
521 best_sibling_candidate_id = current_node_index;
522 }
523
524 // If this is not a leaf, it's possible a better cost could be found further down.
525 if !candidate.is_leaf() {
526 let inherited_cost = total_cost - candidate.aabb().half_area();
527 let min_subtree_cost = new_node_cost + inherited_cost;
528 if min_subtree_cost < min_cost {
529 stack.push(SiblingInsertionCandidate {
530 inherited_cost,
531 index: candidate.first_index,
532 });
533 stack.push(SiblingInsertionCandidate {
534 inherited_cost,
535 index: candidate.first_index + 1,
536 });
537 max_stack_len = stack.len().max(max_stack_len);
538 }
539 }
540 }
541
542 if max_stack_len * 2 > stack.cap() {
543 stack.reserve(max_stack_len * 2);
544 }
545
546 let best_sibling_candidate = self.nodes[best_sibling_candidate_id];
547
548 // To avoid having gaps or re-arranging the BVH:
549 // The new parent goes in the sibling's position.
550 // The sibling and new node go on the end.
551 let new_sibling_id = self.nodes.len() as u32;
552 let new_parent = Bvh2Node::new(
553 new_node.aabb().union(best_sibling_candidate.aabb()),
554 0,
555 new_sibling_id,
556 );
557
558 // New parent goes in the sibling's position.
559 let new_parent_id = best_sibling_candidate_id;
560 self.nodes[new_parent_id] = new_parent;
561
562 self.nodes.push(best_sibling_candidate);
563 let new_node_id = self.nodes.len();
564 self.nodes.push(new_node); // Put the new node at the very end.
565 self.parents.push(new_parent_id as u32);
566 self.parents.push(new_parent_id as u32);
567
568 // Tell the children (or primitives) of the moved sibling where it went.
569 self.relink_node(&best_sibling_candidate, new_sibling_id as usize);
570
571 if !best_sibling_candidate.is_leaf() {
572 // The sibling was moved to the end of the node list, but its children stayed where they were,
573 // so they now come before their parent.
574 self.children_are_ordered_after_parents = false;
575 }
576
577 // if primitives_to_nodes has already been initialized
578 if !self.primitives_to_nodes.is_empty() {
579 // Tell primitives where their node went.
580 let end = new_node.first_index + new_node.prim_count;
581 if self.primitives_to_nodes.len() < end as usize {
582 // Since we are adding a primitive it's possible that primitives_to_nodes is not large enough yet.
583 self.primitives_to_nodes.resize(end as usize, INVALID);
584 }
585 update_primitives_to_nodes_for_node(
586 &new_node,
587 new_node_id,
588 &self.primitive_indices,
589 &mut self.primitives_to_nodes,
590 )
591 }
592
593 // Need to work up the tree updating the aabbs since we just added a node.
594 // A rotation along the way can move the node we just attached, so we track it.
595 self.refit_and_rotate_from_tracking_fast(new_parent_id, new_node_id)
596 }
597
598 /// Grows [`Bvh2::max_depth`] to account for a leaf that was just inserted below a sibling
599 /// at `sibling_depth`, where the root has a depth of 0.
600 ///
601 /// Note this is an upper bound estimate, not a strict bound. If the sibling is an inner node,
602 /// its whole subtree is pushed one level deeper too, and the greedy descent never visits that subtree,
603 /// so its height isn't known here.
604 #[inline]
605 fn update_max_depth_for_greedy_insertion(&mut self, sibling_depth: usize) {
606 // + 1 for the new leaf below the sibling
607 // + 1 because `max_depth` is a traversal stack size rather than a node depth
608 // + 1 more for a rotation that may push the new leaf one level deeper
609 self.max_depth = self.max_depth.max(sibling_depth + 3);
610 }
611
612 /// Searches the tree for the best sibling for a leaf with the given `aabb` using a greedy top-down descent.
613 /// The best sibling is classified as the sibling that if chosen it would increase the surface area of the BVH the least.
614 ///
615 /// Unlike the branch and bound search in [`Bvh2::insert_leaf_precise()`], this only ever descends a single path from the root,
616 /// always taking the child with the lower bound on what inserting beneath it could cost, and stopping as soon as
617 /// neither child's bound can beat the best candidate already found. This is O(depth), needs no traversal stack,
618 /// and doesn't blow up the same way as the BVH gets deeper. However, it tends to find a slightly worse sibling
619 /// than the branch and bound search.
620 ///
621 /// # Returns
622 /// The index of the best sibling found, and the depth it was found at (the root has a depth of 0).
623 ///
624 /// # Arguments
625 /// * `aabb` - The aabb of the leaf that is going to be inserted.
626 pub fn find_sibling_greedy(&self, aabb: &Aabb) -> (usize, usize) {
627 // This is based on `b2FindBestSibling` in Box2D by Erin Catto.
628
629 let center = aabb.center();
630 let area = aabb.half_area();
631
632 let root_aabb = self.nodes[0].aabb();
633
634 // Area of the node currently being descended into.
635 let mut area_base = root_aabb.half_area();
636
637 // Area of that node once it has been inflated to also contain the new leaf.
638 let mut direct_cost = root_aabb.union(aabb).half_area();
639
640 // How much every node from the root down to the current node would have to grow.
641 let mut inherited_cost = 0.0;
642
643 let mut best_cost = direct_cost;
644 let mut best_sibling_candidate_id = 0;
645 let mut best_sibling_candidate_depth = 0;
646
647 let mut current_node_index = 0;
648 let mut depth = 0;
649
650 // Descend the tree from the root, following a single greedy path.
651 while !self.nodes[current_node_index].is_leaf() {
652 let left_id = self.nodes[current_node_index].first_index as usize;
653 let right_id = left_id + 1;
654
655 // Cost of creating a new parent for this node and the new leaf.
656 let total_cost = direct_cost + inherited_cost;
657 if total_cost < best_cost {
658 best_cost = total_cost;
659 best_sibling_candidate_id = current_node_index;
660 best_sibling_candidate_depth = depth;
661 }
662
663 inherited_cost += direct_cost - area_base;
664
665 let left = &self.nodes[left_id];
666 let right = &self.nodes[right_id];
667 let left_is_leaf = left.is_leaf();
668 let right_is_leaf = right.is_leaf();
669 let left_aabb = left.aabb();
670 let right_aabb = right.aabb();
671 let left_direct_cost = left_aabb.union(aabb).half_area();
672 let right_direct_cost = right_aabb.union(aabb).half_area();
673
674 // Lower bound on the cost of choosing a sibling from the subtree rooted at each child.
675 // Left at f32::MAX for leaves since there is nothing below them to descend into.
676 let mut left_lower_cost = f32::MAX;
677 let mut right_lower_cost = f32::MAX;
678 let mut left_area = 0.0;
679 let mut right_area = 0.0;
680
681 if left_is_leaf {
682 let cost = left_direct_cost + inherited_cost;
683 if cost < best_cost {
684 best_cost = cost;
685 best_sibling_candidate_id = left_id;
686 best_sibling_candidate_depth = depth + 1;
687 }
688 } else {
689 left_area = left_aabb.half_area();
690
691 // Lower bound cost of choosing a sibling from the subtree rooted at the left child.
692 //
693 // This is the minimum of:
694 //
695 // 1. Choosing the child itself: inherited_cost + left_direct_cost
696 // 2. Choosing one of its descendants: inherited_cost + (left_direct_cost - left_area) + area
697 left_lower_cost = inherited_cost + left_direct_cost + (area - left_area).min(0.0);
698 }
699
700 if right_is_leaf {
701 let cost = right_direct_cost + inherited_cost;
702 if cost < best_cost {
703 best_cost = cost;
704 best_sibling_candidate_id = right_id;
705 best_sibling_candidate_depth = depth + 1;
706 }
707 } else {
708 right_area = right_aabb.half_area();
709
710 // See above for left_lower_cost.
711 right_lower_cost =
712 inherited_cost + right_direct_cost + (area - right_area).min(0.0);
713 }
714
715 // Note: Box2D also has a separate early out if both children are leaves,
716 // but because the costs for leaves are left as f32::MAX, this check
717 // handles that too.
718 if best_cost <= left_lower_cost && best_cost <= right_lower_cost {
719 // Neither subtree can beat the best candidate already found.
720 break;
721 }
722
723 if left_lower_cost == right_lower_cost && !left_is_leaf {
724 // The bounds give no clear choice, which happens when both children
725 // fully contain `aabb`. Fall back to the child whose center is closest.
726 left_lower_cost = (left_aabb.center() - center).length_squared();
727 right_lower_cost = (right_aabb.center() - center).length_squared();
728 }
729
730 // Descend into the more promising child.
731 if left_lower_cost < right_lower_cost && !left_is_leaf {
732 current_node_index = left_id;
733 area_base = left_area;
734 direct_cost = left_direct_cost;
735 } else {
736 current_node_index = right_id;
737 area_base = right_area;
738 direct_cost = right_direct_cost;
739 }
740
741 depth += 1;
742 }
743
744 (best_sibling_candidate_id, best_sibling_candidate_depth)
745 }
746
747 /// Computes the cost of a potential rotation around the node at `node_id`,
748 /// and applies it if it improves the total cost of the two inner nodes below `node_id`.
749 ///
750 /// This is a local restructuring of the tree that swaps one of the two children of `node_id`
751 /// with a child of the other (a grandchild of `node_id`). Given a tree of the form:
752 ///
753 /// ```text
754 /// node
755 /// / \
756 /// b c
757 /// / \ / \
758 /// d e f g
759 ///
760 /// ```
761 ///
762 /// where `node_id` refers to `node`, four rotations are considered: swapping `b` with `f`,
763 /// `b` with `g`, `c` with `d`, and `c` with `e`. The minimum cost of the four is computed,
764 /// and if it is less than the current cost of `b` and `c`, the rotation is applied.
765 ///
766 /// This incrementally recovers the quality of the BVH as leaves are inserted or the tree is refit,
767 /// and helps avoid degenerating depth.
768 ///
769 /// # Returns
770 /// The two slots whose contents were swapped, if a rotation was applied. Any node id held by the caller
771 /// that is one of the two needs to be updated to the other.
772 ///
773 /// # Arguments
774 /// * `node_id` - The index into `self.nodes` of the node to rotate around. Does nothing for leaves,
775 /// or for nodes whose children are both leaves.
776 pub fn rotate_node(&mut self, node_id: usize) -> Option<(usize, usize)> {
777 // This is based on `b2RotateNodes` in Box2D by Erin Catto.
778
779 let node = self.nodes[node_id];
780 if node.is_leaf() {
781 return None;
782 }
783
784 let b_id = node.first_index as usize;
785 let c_id = b_id + 1;
786 let b = self.nodes[b_id];
787 let c = self.nodes[c_id];
788
789 let b_area = b.aabb().half_area();
790 let c_area = c.aabb().half_area();
791 let mut best_cost = b_area + c_area;
792
793 /// The two slots to swap, the node that ends up with a different pair of children,
794 /// and the aabb it ends up with.
795 struct RotationCandidate {
796 from: usize,
797 to: usize,
798 refit_id: usize,
799 refit_aabb: Aabb,
800 }
801
802 let mut best_rotation: Option<RotationCandidate> = None;
803
804 if !c.is_leaf() {
805 let f_id = c.first_index as usize;
806 let g_id = f_id + 1;
807 let f_aabb = self.nodes[f_id].aabb();
808 let g_aabb = self.nodes[g_id].aabb();
809
810 // Cost of swapping b and f, which leaves c covering b and g.
811 //
812 // node
813 // / \
814 // f c
815 // / \
816 // b g
817 // / \
818 // d e
819 let aabb_bg = b.aabb().union(g_aabb);
820 let cost_bf = b_area + aabb_bg.half_area();
821 if cost_bf < best_cost {
822 best_cost = cost_bf;
823 best_rotation = Some(RotationCandidate {
824 from: b_id,
825 to: f_id,
826 refit_id: c_id,
827 refit_aabb: aabb_bg,
828 });
829 }
830
831 // Cost of swapping b and g, which leaves c covering b and f.
832 //
833 // node
834 // / \
835 // g c
836 // / \
837 // f b
838 // / \
839 // d e
840 let aabb_bf = b.aabb().union(f_aabb);
841 let cost_bg = b_area + aabb_bf.half_area();
842 if cost_bg < best_cost {
843 best_cost = cost_bg;
844 best_rotation = Some(RotationCandidate {
845 from: b_id,
846 to: g_id,
847 refit_id: c_id,
848 refit_aabb: aabb_bf,
849 });
850 }
851 }
852
853 if !b.is_leaf() {
854 let d_id = b.first_index as usize;
855 let e_id = d_id + 1;
856 let d_aabb = self.nodes[d_id].aabb();
857 let e_aabb = self.nodes[e_id].aabb();
858
859 // Cost of swapping c and d, which leaves b covering c and e.
860 //
861 // node
862 // / \
863 // b d
864 // / \
865 // c e
866 // / \
867 // f g
868 let aabb_ce = c.aabb().union(e_aabb);
869 let cost_cd = c_area + aabb_ce.half_area();
870 if cost_cd < best_cost {
871 best_cost = cost_cd;
872 best_rotation = Some(RotationCandidate {
873 from: c_id,
874 to: d_id,
875 refit_id: b_id,
876 refit_aabb: aabb_ce,
877 });
878 }
879
880 // Cost of swapping c and e, which leaves b covering c and d.
881 //
882 // node
883 // / \
884 // b e
885 // / \
886 // d c
887 // / \
888 // f g
889 let aabb_cd = c.aabb().union(d_aabb);
890 let cost_ce = c_area + aabb_cd.half_area();
891 if cost_ce < best_cost {
892 // Last candidate, so best_cost doesn't need to be updated.
893 best_rotation = Some(RotationCandidate {
894 from: c_id,
895 to: e_id,
896 refit_id: b_id,
897 refit_aabb: aabb_cd,
898 });
899 }
900 }
901
902 let Some(RotationCandidate {
903 from,
904 to,
905 refit_id,
906 refit_aabb,
907 }) = best_rotation
908 else {
909 // No rotation improves the cost.
910 return None;
911 };
912
913 // Box2D reassigns child pointers, but obvhs stores children as an adjacent pair.
914 // Swapping the contents of the two slots has the same structural effect.
915 self.nodes.swap(from, to);
916 self.relink(from);
917 self.relink(to);
918
919 // Only the node that took on a different pair of children needs its aabb recomputed.
920 self.nodes[refit_id].set_aabb(refit_aabb);
921
922 self.children_are_ordered_after_parents = false;
923
924 Some((from, to))
925 }
926
927 /// Refit the BVH working up the tree from this node, ignoring leaves,
928 /// performing rotations at each node along the way where beneficial.
929 ///
930 /// Halts once two consecutive levels needed neither a resize nor a rotation.
931 /// Panics in debug if some parent still needed to be resized.
932 ///
933 /// This can only be used to refit when a single node has changed or moved.
934 ///
935 /// See [`Bvh2::rotate_node()`] and [`Bvh2::refit_from()`].
936 pub fn refit_and_rotate_from_fast(&mut self, index: usize) {
937 self.refit_and_rotate_from_tracking_fast(index, INVALID as usize);
938 }
939
940 /// Same as [`Bvh2::refit_and_rotate_from_fast()`], but follows the node at `tracked_node_id`
941 /// through any rotations applied along the way.
942 ///
943 /// # Returns
944 /// The index that the tracked node ended up at.
945 pub fn refit_and_rotate_from_tracking_fast(
946 &mut self,
947 mut index: usize,
948 mut tracked_node_id: usize,
949 ) -> usize {
950 self.init_parents_if_uninit();
951 let mut unchanged_count = 0;
952 #[cfg(debug_assertions)]
953 let mut stopped = false;
954 loop {
955 let node = self.nodes[index];
956 if !node.is_leaf() {
957 let first_child_bbox = *self.nodes[node.first_index as usize].aabb();
958 let second_child_bbox = *self.nodes[node.first_index as usize + 1].aabb();
959 let new_aabb = first_child_bbox.union(&second_child_bbox);
960
961 let aabb_unchanged = self.nodes[index].aabb() == &new_aabb;
962
963 self.nodes[index].set_aabb(new_aabb);
964
965 #[cfg(debug_assertions)]
966 if stopped {
967 // Debug builds keep going to check that nothing above actually needed refitting,
968 // but it can't perform any more rotations so that `tracked_node_id` is stable.
969 debug_assert!(
970 aabb_unchanged,
971 "Some parents still needed refitting. Unideal fitting is occurring somewhere."
972 );
973 if index == 0 {
974 break;
975 }
976 index = self.parents[index] as usize;
977 continue;
978 }
979
980 // A rotation only swaps nodes below `index`, so the computed aabb stays valid.
981 let rotation = self.rotate_node(index);
982 if let Some((from, to)) = rotation {
983 if tracked_node_id == from {
984 tracked_node_id = to;
985 } else if tracked_node_id == to {
986 tracked_node_id = from;
987 }
988 }
989
990 // Two consecutive levels that didn't need a resize or a rotation mean that
991 // the ancestors above see no change in their children's or grandchildren's aabbs,
992 // which is what rotation candidates are based on, so we can stop early.
993 //
994 // Note that they could still have rotations that have not been evaluated,
995 // so this is a trade-off.
996 if aabb_unchanged && rotation.is_none() {
997 unchanged_count += 1;
998 if unchanged_count == 2 {
999 #[cfg(not(debug_assertions))]
1000 return tracked_node_id;
1001 #[cfg(debug_assertions)]
1002 {
1003 stopped = true;
1004 }
1005 }
1006 } else {
1007 unchanged_count = 0;
1008 }
1009 }
1010 if index == 0 {
1011 break;
1012 }
1013 index = self.parents[index] as usize;
1014 }
1015 tracked_node_id
1016 }
1017}
1018
1019/// Slow at building, makes a slow bvh, just for testing insertion.
1020/// Can result in very deep BVHs in some cases.
1021///
1022/// Dramatically slower at building than ploc. Rotations keep the tree from degenerating
1023/// as primitives are inserted.
1024#[doc(hidden)]
1025pub fn build_bvh2_by_precise_insertion<T: Boundable>(primitives: &[T]) -> Bvh2 {
1026 let mut bvh = Bvh2::default();
1027
1028 let mut stack = HeapStack::new_with_capacity(1000);
1029
1030 for prim_id in 0..primitives.len() {
1031 bvh.insert_primitive_precise(primitives[prim_id].aabb(), prim_id as u32, &mut stack);
1032 }
1033
1034 // Update max depth for validate
1035 if !bvh.nodes.is_empty() {
1036 bvh.max_depth = (bvh.depth(0) + 1).max(DEFAULT_MAX_STACK_DEPTH);
1037 }
1038
1039 #[cfg(debug_assertions)]
1040 {
1041 bvh.validate(primitives, false, true);
1042 }
1043
1044 bvh
1045}
1046
1047/// Same as [`build_bvh2_by_precise_insertion()`], but with the faster greedy sibling search,
1048/// just for testing insertion.
1049///
1050/// Still slow at building compared to ploc, and can result in lower quality trees
1051/// than [`build_bvh2_by_precise_insertion()`]. Rotations keep the tree from degenerating
1052/// as primitives are inserted.
1053#[doc(hidden)]
1054pub fn build_bvh2_by_insertion<T: Boundable>(primitives: &[T]) -> Bvh2 {
1055 let mut bvh = Bvh2::default();
1056
1057 for prim_id in 0..primitives.len() {
1058 bvh.insert_primitive(primitives[prim_id].aabb(), prim_id as u32);
1059 }
1060
1061 #[cfg(debug_assertions)]
1062 {
1063 bvh.validate(primitives, false, true);
1064 }
1065
1066 bvh
1067}
1068
1069/// Just here to for testing/benchmarking/validating leaf removal and insertion. See reinsertion.rs if you want to
1070/// optimize a BVH2. This currently tends to make a good BVH slower despite applying rotations during insertion.
1071#[doc(hidden)]
1072pub fn slow_leaf_reinsertion(bvh: &mut Bvh2) {
1073 let mut stack = HeapStack::new_with_capacity(1000);
1074 for node_id in 1..bvh.nodes.len() {
1075 if bvh.nodes.len() <= node_id {
1076 break;
1077 }
1078 if bvh.nodes[node_id].is_leaf() {
1079 // Assert that the parent of this node is not a leaf (a parent could never be a leaf)
1080 assert!(!bvh.nodes[bvh.parents[node_id] as usize].is_leaf());
1081 // If the node is a leaf, remove it
1082 let removed_leaf = bvh.remove_leaf(node_id);
1083 // Insert it again, maybe it will find a better spot
1084 bvh.insert_leaf_precise(removed_leaf, &mut stack);
1085 }
1086 }
1087 #[cfg(debug_assertions)]
1088 {
1089 bvh.validate_parents();
1090 }
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095 use std::time::Duration;
1096
1097 use glam::vec3a;
1098
1099 use super::*;
1100 use crate::{BvhBuildParams, bvh2::builder::build_bvh2, test_util::geometry::demoscene};
1101
1102 /// `count` unit boxes laid out in sorted order along a line.
1103 /// This is the input that degenerates a rotation-free insertion build into a list.
1104 fn sorted_boxes(count: usize) -> Vec<Aabb> {
1105 (0..count)
1106 .map(|i| {
1107 let center = vec3a(i as f32, 0.0, 0.0);
1108 Aabb::new(center - 0.5, center + 0.5)
1109 })
1110 .collect()
1111 }
1112
1113 #[test]
1114 fn build_by_precise_insertion() {
1115 for res in 30..=32 {
1116 let tris = demoscene(res, 0);
1117 let bvh = build_bvh2_by_precise_insertion(&tris);
1118 assert_eq!(bvh.active_primitive_indices_count(), tris.len());
1119 assert_eq!(bvh.nodes.len(), tris.len() * 2 - 1);
1120 assert_eq!(bvh.primitives_to_nodes.len(), tris.len());
1121 bvh.validate(&tris, false, false);
1122 }
1123 }
1124
1125 #[test]
1126 fn build_by_insertion() {
1127 for res in 30..=32 {
1128 let tris = demoscene(res, 0);
1129 let bvh = build_bvh2_by_insertion(&tris);
1130 bvh.validate(&tris, false, true);
1131 }
1132 }
1133
1134 #[test]
1135 fn test_slow_leaf_reinsertion() {
1136 for res in 30..=32 {
1137 let tris = demoscene(res, 0);
1138
1139 let mut bvh = build_bvh2(
1140 &tris,
1141 BvhBuildParams::medium_build(),
1142 &mut Duration::default(),
1143 );
1144 bvh.init_primitives_to_nodes_if_uninit();
1145 bvh.init_parents_if_uninit();
1146 slow_leaf_reinsertion(&mut bvh);
1147 bvh.validate(&tris, false, false);
1148 bvh.reorder_in_stack_traversal_order();
1149 bvh.validate(&tris, false, false);
1150 }
1151 }
1152
1153 #[test]
1154 fn remove_all_primitives() {
1155 let tris = demoscene(16, 0);
1156
1157 // Test with both a bvh that only has one primitive per leaf
1158 // and also with one that has multiple primitives per leaf.
1159 let bvh1 = build_bvh2(
1160 &tris,
1161 BvhBuildParams::fastest_build(),
1162 &mut Duration::default(),
1163 );
1164 let bvh2 = build_bvh2(
1165 &tris,
1166 BvhBuildParams::medium_build(),
1167 &mut Duration::default(),
1168 );
1169
1170 let mut found_leaf_with_multiple_nodes = false;
1171 for node in &bvh2.nodes {
1172 if node.prim_count > 1 {
1173 found_leaf_with_multiple_nodes = true;
1174 break;
1175 }
1176 }
1177 if !found_leaf_with_multiple_nodes {
1178 panic!(
1179 "Test remove_all_primitives bvh2 should have some nodes that contain multiple primitives"
1180 );
1181 }
1182
1183 for bvh in &mut [bvh1, bvh2] {
1184 bvh.init_primitives_to_nodes_if_uninit();
1185 bvh.init_parents_if_uninit();
1186 bvh.validate(&tris, false, false);
1187
1188 for primitive_id in 0..tris.len() as u32 {
1189 bvh.remove_primitive(primitive_id);
1190 bvh.validate(&tris, false, false);
1191 }
1192
1193 assert_eq!(bvh.nodes.len(), 0);
1194 assert_eq!(bvh.parents.len(), 0);
1195 assert_eq!(bvh.primitives_to_nodes.len(), 0);
1196 bvh.validate(&tris, false, false);
1197 }
1198 }
1199
1200 #[test]
1201 fn remove_and_insert_all_primitives() {
1202 let tris = demoscene(16, 0);
1203
1204 let mut bvh = build_bvh2(
1205 &tris,
1206 BvhBuildParams::medium_build(),
1207 &mut Duration::default(),
1208 );
1209 bvh.init_primitives_to_nodes_if_uninit();
1210 bvh.init_parents_if_uninit();
1211 bvh.validate(&tris, false, false);
1212
1213 let mut stack = HeapStack::new_with_capacity(1000);
1214
1215 for primitive_id in 0..tris.len() as u32 {
1216 bvh.remove_primitive(primitive_id);
1217 bvh.validate(&tris, false, false);
1218 }
1219
1220 for primitive_id in 0..tris.len() as u32 {
1221 bvh.insert_primitive_precise(
1222 tris[primitive_id as usize].aabb(),
1223 primitive_id,
1224 &mut stack,
1225 );
1226 bvh.validate_primitives_to_nodes();
1227 }
1228
1229 bvh.validate(&tris, false, false);
1230 }
1231
1232 #[test]
1233 fn insert_leaf_precise_clears_children_are_ordered_after_parents() {
1234 // Regression test
1235 //
1236 // If `Bvh2::children_are_ordered_after_parents` isn't cleared when inserting primitives,
1237 // `Bvh2::refit_all()` takes the fast path, and computes some parents from children
1238 // it hasn't updated yet, leaving nodes that don't fit their children.
1239
1240 let tris = demoscene(16, 0);
1241
1242 let mut bvh = build_bvh2(
1243 &tris,
1244 BvhBuildParams::medium_build(),
1245 &mut Duration::default(),
1246 );
1247 bvh.init_primitives_to_nodes_if_uninit();
1248 bvh.init_parents_if_uninit();
1249
1250 // Remove and re-insert some primitives.
1251 let mut stack = HeapStack::new_with_capacity(1000);
1252 let primitive_ids = (0..tris.len() as u32).step_by(32).collect::<Vec<_>>();
1253
1254 for &primitive_id in &primitive_ids {
1255 bvh.remove_primitive(primitive_id);
1256 }
1257
1258 // Removal clears `children_are_ordered_after_parents`, so we restore the ordering
1259 // here so that we can test that insertion clears it again.
1260 bvh.reorder_in_stack_traversal_order();
1261 assert!(bvh.children_are_ordered_after_parents);
1262
1263 for &primitive_id in &primitive_ids {
1264 let mut aabb = tris[primitive_id as usize].aabb();
1265 aabb.min -= 0.05;
1266 aabb.max += 0.05;
1267 bvh.insert_primitive_precise(aabb, primitive_id, &mut stack);
1268 }
1269
1270 bvh.max_depth = (bvh.depth(0) + 1).max(DEFAULT_MAX_STACK_DEPTH);
1271
1272 bvh.validate(&tris, false, false);
1273
1274 // Grow every leaf, then refit the whole bvh.
1275 for node in &mut bvh.nodes {
1276 if node.is_leaf() {
1277 let mut aabb = *node.aabb();
1278 aabb.max += 0.1;
1279 node.set_aabb(aabb);
1280 }
1281 }
1282 bvh.refit_all();
1283
1284 // Every inner node should tightly fit its children.
1285 for (node_id, node) in bvh.nodes.iter().enumerate() {
1286 if !node.is_leaf() {
1287 let children_aabb = bvh.nodes[node.first_index as usize]
1288 .aabb()
1289 .union(bvh.nodes[node.first_index as usize + 1].aabb());
1290 assert_eq!(
1291 *node.aabb(),
1292 children_aabb,
1293 "node {node_id} does not fit its children after refit_all()"
1294 );
1295 }
1296 }
1297 }
1298
1299 #[test]
1300 fn insertion_maintains_max_depth() {
1301 // The greedy descent knows the depth of the sibling it picked, so insertion can keep `max_depth` up to date.
1302 // Bvh2::validate() requires it to be a valid stack size, so a stale `max_depth` would fail the validation below.
1303 let tris = demoscene(32, 0);
1304 let bvh = build_bvh2_by_insertion(&tris);
1305
1306 assert!(
1307 bvh.depth(0) <= bvh.max_depth,
1308 "max_depth ({}) does not cover the actual bvh depth ({})",
1309 bvh.max_depth,
1310 bvh.depth(0)
1311 );
1312 bvh.validate(&tris, false, true);
1313 }
1314
1315 #[test]
1316 fn insertion_rotations_prevent_degenerate_bvhs() {
1317 // Without rotations, this sorted input degenerates either insertion method into a list.
1318 let boxes = sorted_boxes(1024);
1319
1320 let mut greedy_bvh = Bvh2::default();
1321 for (prim_id, aabb) in boxes.iter().enumerate() {
1322 greedy_bvh.insert_primitive(*aabb, prim_id as u32);
1323 }
1324 greedy_bvh.validate(&boxes, false, true);
1325
1326 let mut precise_bvh = Bvh2::default();
1327 let mut stack = HeapStack::new_with_capacity(1000);
1328 for (prim_id, aabb) in boxes.iter().enumerate() {
1329 let node_id = precise_bvh.insert_primitive_precise(*aabb, prim_id as u32, &mut stack);
1330 assert_eq!(precise_bvh.primitives_to_nodes[prim_id], node_id as u32);
1331 }
1332 precise_bvh.validate(&boxes, false, true);
1333
1334 for bvh in [&greedy_bvh, &precise_bvh] {
1335 let depth = bvh.depth(0);
1336 assert!(
1337 depth * 8 < boxes.len(),
1338 "rotations should keep the BVH far shallower than a list, got depth {depth}"
1339 );
1340 }
1341 }
1342
1343 #[test]
1344 fn rotate_node_preserves_aabbs() {
1345 let tris = demoscene(24, 0);
1346
1347 let mut bvh = build_bvh2(
1348 &tris,
1349 BvhBuildParams::fastest_build(),
1350 &mut Duration::default(),
1351 );
1352 bvh.init_primitives_to_nodes_if_uninit();
1353 bvh.init_parents_if_uninit();
1354
1355 for node_id in 0..bvh.nodes.len() {
1356 let aabb = *bvh.nodes[node_id].aabb();
1357 bvh.rotate_node(node_id);
1358 assert_eq!(
1359 *bvh.nodes[node_id].aabb(),
1360 aabb,
1361 "rotating node {node_id} changed its aabb"
1362 );
1363 bvh.validate(&tris, false, true);
1364 }
1365 }
1366
1367 #[test]
1368 fn move_all_primitives() {
1369 let tris = demoscene(16, 0);
1370
1371 let mut bvh = build_bvh2(
1372 &tris,
1373 BvhBuildParams::fastest_build(),
1374 &mut Duration::default(),
1375 );
1376 bvh.init_primitives_to_nodes_if_uninit();
1377 bvh.init_parents_if_uninit();
1378 bvh.validate(&tris, false, false);
1379
1380 let node_count = bvh.nodes.len();
1381 let primitive_indices = bvh.primitive_indices.clone();
1382
1383 // Move every primitive to a grown aabb, then back to its original one.
1384 for primitive_id in 0..tris.len() as u32 {
1385 let mut aabb = tris[primitive_id as usize].aabb();
1386 aabb.min -= 0.05;
1387 aabb.max += 0.05;
1388 bvh.move_primitive(aabb, primitive_id);
1389 assert_eq!(bvh.nodes.len(), node_count);
1390 bvh.validate_parents();
1391 bvh.validate_primitives_to_nodes();
1392 }
1393 for primitive_id in 0..tris.len() as u32 {
1394 bvh.move_primitive(tris[primitive_id as usize].aabb(), primitive_id);
1395 assert_eq!(bvh.nodes.len(), node_count);
1396 }
1397
1398 assert_eq!(bvh.primitive_indices, primitive_indices);
1399 bvh.validate(&tris, false, true);
1400 }
1401
1402 #[test]
1403 fn move_leaf_with_single_node_bvh() {
1404 let tris = demoscene(16, 0);
1405
1406 let mut bvh = Bvh2::default();
1407 bvh.insert_primitive(tris[0].aabb(), 0);
1408 assert_eq!(bvh.nodes.len(), 1);
1409
1410 let mut aabb = tris[0].aabb();
1411 aabb.max += 1.0;
1412 assert_eq!(bvh.move_leaf(0, aabb), 0);
1413 assert_eq!(bvh.nodes.len(), 1);
1414 assert_eq!(*bvh.nodes[0].aabb(), aabb);
1415
1416 bvh.insert_primitive(tris[1].aabb(), 1);
1417 bvh.move_primitive(tris[0].aabb(), 0);
1418 bvh.validate(&tris[0..2], false, true);
1419 }
1420}