parry2d/partitioning/bvh/bvh_insert.rs
1use super::bvh_tree::{BvhNodeIndex, BvhNodeWide};
2use super::BvhNode;
3use crate::bounding_volume::{Aabb, BoundingVolume};
4use crate::math::{Real, Vector};
5use crate::partitioning::Bvh;
6use alloc::vec;
7
8impl Bvh {
9 /// Inserts a new leaf into the BVH or updates an existing one.
10 ///
11 /// If a leaf with the given `leaf_index` already exists in the tree, its AABB is updated
12 /// to the new value and the tree's internal nodes are adjusted to maintain correctness.
13 /// If the leaf doesn't exist, it's inserted into an optimal position based on the
14 /// Surface Area Heuristic (SAH).
15 ///
16 /// This operation automatically propagates AABB changes up the tree to maintain the
17 /// invariant that each internal node's AABB encloses all its descendants. For better
18 /// performance when updating many leaves, consider using [`insert_or_update_partially`]
19 /// followed by a single [`refit`] call.
20 ///
21 /// # Arguments
22 ///
23 /// * `aabb` - The Axis-Aligned Bounding Box for this leaf
24 /// * `leaf_index` - A unique identifier for this leaf (typically an object ID)
25 ///
26 /// # Performance
27 ///
28 /// - **Insert new leaf**: O(log n) average, O(n) worst case
29 /// - **Update existing leaf**: O(log n) for propagation up the tree
30 ///
31 /// # Examples
32 ///
33 /// ## Adding new objects
34 ///
35 /// ```
36 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
37 /// use parry3d::partitioning::Bvh;
38 /// use parry3d::bounding_volume::Aabb;
39 /// use nalgebra::Point3;
40 ///
41 /// let mut bvh = Bvh::new();
42 ///
43 /// // Insert objects with custom IDs
44 /// bvh.insert(Aabb::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0)), 100);
45 /// bvh.insert(Aabb::new(Point3::new(5.0, 0.0, 0.0), Point3::new(6.0, 1.0, 1.0)), 200);
46 /// bvh.insert(Aabb::new(Point3::new(10.0, 0.0, 0.0), Point3::new(11.0, 1.0, 1.0)), 300);
47 ///
48 /// assert_eq!(bvh.leaf_count(), 3);
49 /// # }
50 /// ```
51 ///
52 /// ## Updating object positions
53 ///
54 /// ```
55 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
56 /// use parry3d::partitioning::Bvh;
57 /// use parry3d::bounding_volume::Aabb;
58 /// use nalgebra::Point3;
59 ///
60 /// let mut bvh = Bvh::new();
61 ///
62 /// // Insert an object
63 /// bvh.insert(Aabb::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0)), 42);
64 ///
65 /// // Simulate the object moving - just insert with the same ID
66 /// bvh.insert(Aabb::new(Point3::new(5.0, 0.0, 0.0), Point3::new(6.0, 1.0, 1.0)), 42);
67 ///
68 /// // The BVH still has only 1 leaf, but at the new position
69 /// assert_eq!(bvh.leaf_count(), 1);
70 /// # }
71 /// ```
72 ///
73 /// ## Bulk updates with better performance
74 ///
75 /// ```
76 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
77 /// use parry3d::partitioning::{Bvh, BvhWorkspace};
78 /// use parry3d::bounding_volume::Aabb;
79 /// use nalgebra::Point3;
80 ///
81 /// let mut bvh = Bvh::new();
82 /// let mut workspace = BvhWorkspace::default();
83 ///
84 /// // Add initial objects
85 /// for i in 0..100 {
86 /// let aabb = Aabb::new(
87 /// Point3::new(i as f32, 0.0, 0.0),
88 /// Point3::new(i as f32 + 1.0, 1.0, 1.0)
89 /// );
90 /// bvh.insert(aabb, i);
91 /// }
92 ///
93 /// // For better performance on bulk updates, use insert_or_update_partially
94 /// // then refit once at the end
95 /// for i in 0..100 {
96 /// let aabb = Aabb::new(
97 /// Point3::new(i as f32 + 0.1, 0.0, 0.0),
98 /// Point3::new(i as f32 + 1.1, 1.0, 1.0)
99 /// );
100 /// bvh.insert_or_update_partially(aabb, i, 0.0);
101 /// }
102 /// bvh.refit(&mut workspace); // Update tree in one pass
103 /// # }
104 /// ```
105 ///
106 /// # Notes
107 ///
108 /// - Leaf indices can be any `u32` value - they don't need to be contiguous
109 /// - The same leaf index can only exist once in the tree
110 /// - For dynamic scenes, call this every frame for moving objects
111 /// - Consider calling [`refit`] or [`optimize_incremental`] periodically for best
112 /// query performance
113 ///
114 /// # See Also
115 ///
116 /// - [`insert_with_change_detection`](Self::insert_with_change_detection) - Insert with
117 /// margin for motion prediction
118 /// - [`insert_or_update_partially`](Self::insert_or_update_partially) - Insert without
119 /// propagation (faster for bulk updates)
120 /// - [`remove`](Self::remove) - Remove a leaf from the tree
121 /// - [`refit`](Self::refit) - Update tree after bulk modifications
122 ///
123 /// [`insert_or_update_partially`]: Self::insert_or_update_partially
124 /// [`refit`]: Self::refit
125 /// [`optimize_incremental`]: Self::optimize_incremental
126 pub fn insert(&mut self, aabb: Aabb, leaf_index: u32) {
127 self.insert_with_change_detection(aabb, leaf_index, 0.0)
128 }
129
130 /// Inserts a leaf into this BVH, or updates it if already exists.
131 ///
132 /// If the `aabb` is already contained by the existing leaf node AABB, nothing is modified.
133 /// Otherwise, the aabb being effectively inserted is equal to `aabb` enlarged by the
134 /// `change_detection_margin`.
135 pub fn insert_with_change_detection(
136 &mut self,
137 aabb: Aabb,
138 leaf_index: u32,
139 change_detection_margin: Real,
140 ) {
141 if let Some(leaf) = self.leaf_node_indices.get(leaf_index as usize) {
142 let node = &mut self.nodes[*leaf];
143
144 if change_detection_margin > 0.0 {
145 if !node.contains_aabb(&aabb) {
146 node.mins = aabb.mins - Vector::repeat(change_detection_margin);
147 node.maxs = aabb.maxs + Vector::repeat(change_detection_margin);
148 node.data.set_change_pending();
149 } else {
150 // No change detected, no propagation needed.
151 return;
152 }
153 } else {
154 node.mins = aabb.mins;
155 node.maxs = aabb.maxs;
156 }
157
158 // Propagate up.
159 // TODO: maybe we should offer multiple propagation strategy.
160 // The one we currently implement simply stops as soon as a
161 // parent node contains the given `aabb`, but it won’t try
162 // to make the parent AABBs smaller even if we could.
163 // There could be two additional strategies that are slower but would leave the
164 // tree in a tighter state:
165 // - Make the parent smaller if possible by merging the aabb with
166 // the sibling.
167 // - In addition to merging with the sibling, we could apply bottom-up
168 // tree rotations to optimize part of the tree on our way up to the
169 // root.
170 let wide_node_id = leaf.decompose().0;
171 if wide_node_id == 0 {
172 // Already at the root, no propagation possible.
173 return;
174 }
175
176 let mut parent = self.parents[wide_node_id];
177 loop {
178 let node = &mut self.nodes[parent];
179 if node.contains_aabb(&aabb) {
180 // No more propagation needed, the parent is big enough.
181 break;
182 }
183
184 node.mins = node.mins.inf(&aabb.mins);
185 node.maxs = node.maxs.sup(&aabb.maxs);
186
187 let wide_node_id = parent.decompose().0;
188 if wide_node_id == 0 {
189 break;
190 }
191
192 parent = self.parents[wide_node_id];
193 }
194 } else {
195 self.insert_new_unchecked(aabb, leaf_index);
196 }
197 }
198
199 /// Either inserts a node on this tree, or, if it already exists, updates its associated bounding
200 /// but doesn’t update its ascendant nodes.
201 ///
202 /// This method is primarily designed to be called for inserting new nodes or updating existing
203 /// ones, and then running a [`Bvh::refit`]. Until [`Bvh::refit`] or [`Bvh::refit_without_opt`]
204 /// is called, the BVH will effectively be left in an invalid state where some internal nodes
205 /// might no longer enclose their children.
206 ///
207 /// For an alternative that inserts a node while also making sure all its ascendants are
208 /// up to date, see [`Bvh::insert`].
209 pub fn insert_or_update_partially(
210 &mut self,
211 aabb: Aabb,
212 leaf_index: u32,
213 change_detection_margin: Real,
214 ) {
215 if let Some(leaf) = self.leaf_node_indices.get(leaf_index as usize) {
216 let node = &mut self.nodes[*leaf];
217
218 if change_detection_margin > 0.0 {
219 if !node.contains_aabb(&aabb) {
220 node.mins = aabb.mins - Vector::repeat(change_detection_margin);
221 node.maxs = aabb.maxs + Vector::repeat(change_detection_margin);
222 node.data.set_change_pending();
223 }
224 } else {
225 node.mins = aabb.mins;
226 node.maxs = aabb.maxs;
227 }
228 } else {
229 self.insert_new_unchecked(aabb, leaf_index);
230 }
231 }
232
233 /// Inserts a new leaf into this BVH without checking if it already exists.
234 fn insert_new_unchecked(&mut self, aabb: Aabb, leaf_index: u32) {
235 let _ = self
236 .leaf_node_indices
237 .insert(leaf_index as usize, BvhNodeIndex::default());
238 let leaf_index_mut = &mut self.leaf_node_indices[leaf_index as usize];
239
240 // If the tree is empty, create the root.
241 if self.nodes.is_empty() {
242 self.nodes.push(BvhNodeWide {
243 left: BvhNode::leaf(aabb, leaf_index),
244 right: BvhNode::zeros(),
245 });
246 self.parents.push(BvhNodeIndex::default());
247 *leaf_index_mut = BvhNodeIndex::left(0);
248 return;
249 }
250
251 // If we have a root, but it is partial, just complete it.
252 if self.nodes[0].right.leaf_count() == 0 {
253 self.nodes[0].right = BvhNode::leaf(aabb, leaf_index);
254 *leaf_index_mut = BvhNodeIndex::right(0);
255 return;
256 }
257
258 // General case: traverse the tree to find room for the new leaf.
259 let mut curr_id = 0u32;
260 let mut path_taken = vec![];
261
262 const APPLY_ROTATIONS_DOWN: bool = true;
263 const APPLY_ROTATIONS_UP: bool = false;
264
265 loop {
266 if APPLY_ROTATIONS_UP {
267 path_taken.push(curr_id);
268 }
269
270 if APPLY_ROTATIONS_DOWN {
271 self.maybe_apply_rotation(curr_id);
272 }
273
274 let curr_node = &self.nodes[curr_id as usize];
275
276 // Need to determine the best side to insert our node.
277 let left = &curr_node.left;
278 let right = &curr_node.right;
279
280 let left_merged_aabb = left.aabb().merged(&aabb);
281 let right_merged_aabb = right.aabb().merged(&aabb);
282
283 let left_merged_vol = left_merged_aabb.volume();
284 let right_merged_vol = right_merged_aabb.volume();
285 let left_vol = left.aabb().volume();
286 let right_vol = right.aabb().volume();
287 let left_count = left.leaf_count();
288 let right_count = right.leaf_count();
289
290 // NOTE: when calculating the SAH cost, we don’t care about dividing by the
291 // parent’s volume since both compared costs use the same factor so
292 // ignoring it doesn’t affect the comparison.
293 let left_cost =
294 left_merged_vol * (left_count + 1) as Real + right_vol * right_count as Real;
295 let right_cost =
296 right_merged_vol * (right_count + 1) as Real + left_vol * left_count as Real;
297
298 // Insert into the branch with lowest post-insertion SAH cost.
299 // If the costs are equal, just pick the branch with the smallest leaf count.
300 if left_cost < right_cost || (left_cost == right_cost && left_count < right_count) {
301 // Insert left. The `left` node will become an internal node.
302 // We create a new wide leaf containing the current and new leaves and
303 // attach it to `left`.
304 if left.is_leaf() {
305 let new_leaf_id = self.nodes.len();
306 let wide_node = BvhNodeWide {
307 left: *left,
308 right: BvhNode::leaf(aabb, leaf_index),
309 };
310 self.nodes.push(wide_node);
311 self.parents.push(BvhNodeIndex::left(curr_id));
312
313 let left = &mut self.nodes[curr_id as usize].left;
314 self.leaf_node_indices[left.children as usize] =
315 BvhNodeIndex::left(new_leaf_id as u32);
316 self.leaf_node_indices[leaf_index as usize] =
317 BvhNodeIndex::right(new_leaf_id as u32);
318
319 left.children = new_leaf_id as u32;
320 left.data.add_leaf_count(1);
321 left.mins = left.mins.inf(&aabb.mins);
322 left.maxs = left.maxs.sup(&aabb.maxs);
323 break;
324 } else {
325 let left = &mut self.nodes[curr_id as usize].left;
326 curr_id = left.children;
327 left.data.add_leaf_count(1);
328 left.mins = left.mins.inf(&aabb.mins);
329 left.maxs = left.maxs.sup(&aabb.maxs);
330 }
331 } else {
332 // Insert right. The `right` node will become an internal node.
333 // We create a new wide leaf containing the current and new leaves and
334 // attach it to `right`.
335 if right.is_leaf() {
336 let new_leaf_id = self.nodes.len();
337 let new_node = BvhNodeWide {
338 left: BvhNode::leaf(aabb, leaf_index),
339 right: *right,
340 };
341 self.nodes.push(new_node);
342 self.parents.push(BvhNodeIndex::right(curr_id));
343
344 let right = &mut self.nodes[curr_id as usize].right;
345 self.leaf_node_indices[leaf_index as usize] =
346 BvhNodeIndex::left(new_leaf_id as u32);
347 self.leaf_node_indices[right.children as usize] =
348 BvhNodeIndex::right(new_leaf_id as u32);
349
350 right.children = new_leaf_id as u32;
351 right.data.add_leaf_count(1);
352 right.mins = right.mins.inf(&aabb.mins);
353 right.maxs = right.maxs.sup(&aabb.maxs);
354 break;
355 } else {
356 let right = &mut self.nodes[curr_id as usize].right;
357 curr_id = right.children;
358 right.data.add_leaf_count(1);
359 right.mins = right.mins.inf(&aabb.mins);
360 right.maxs = right.maxs.sup(&aabb.maxs);
361 }
362 }
363 }
364
365 if APPLY_ROTATIONS_UP {
366 while let Some(node) = path_taken.pop() {
367 self.maybe_apply_rotation(node);
368 }
369 }
370 }
371
372 // Applies a tree rotation at the given `node` if this improves the SAH metric at that node.
373 fn maybe_apply_rotation(&mut self, node_id: u32) {
374 let node = self.nodes[node_id as usize];
375 let left = &node.left;
376 let right = &node.right;
377
378 let curr_score =
379 left.volume() * left.leaf_count() as Real + right.volume() * right.leaf_count() as Real;
380
381 macro_rules! eval_costs {
382 ($left: ident, $right: ident) => {
383 if !$left.is_leaf() {
384 let children = self.nodes[$left.children as usize];
385 let left_child = &children.left;
386 let right_child = &children.right;
387
388 // New SAH score after transforming [{left_child, right_child}, right]
389 // into [left_child, {right_child, right}].
390 let new_score1 = left_child.volume() * left_child.leaf_count() as Real
391 + right_child.merged_volume($right)
392 * (right_child.leaf_count() + $right.leaf_count()) as Real;
393
394 // New SAH score after transforming [{left_child, right_child}, right]
395 // into [right_child, {left_child, right}].
396 let new_score2 = right_child.volume() * right_child.leaf_count() as Real
397 + left_child.merged_volume($right)
398 * (left_child.leaf_count() + $right.leaf_count()) as Real;
399
400 if new_score1 < new_score2 {
401 (new_score1 - curr_score, true)
402 } else {
403 (new_score2 - curr_score, false)
404 }
405 } else {
406 (Real::MAX, false)
407 }
408 };
409 }
410
411 // Because of the rotation some leaves might have changed location.
412 // This a helper to update the `leaf_data` map accordingly.
413 macro_rules! set_leaf_data {
414 ($leaf_data_id: ident, $node_id: ident, $left_or_right: expr) => {
415 self.leaf_node_indices[$leaf_data_id as usize] =
416 BvhNodeIndex::new($node_id, $left_or_right);
417 };
418 }
419
420 // For right rotation.
421 let (rotation_score0, left_child_moves_up0) = eval_costs!(left, right);
422 // For left rotation.
423 let (rotation_score1, left_child_moves_up1) = eval_costs!(right, left);
424
425 if rotation_score0 < 0.0 || rotation_score1 < 0.0 {
426 // At least one of the rotations is worth it, apply the one with
427 // the best impact on SAH scoring.
428 if rotation_score0 < rotation_score1 {
429 // Apply RIGHT rotation.
430 let children_id = left.children;
431 let children = self.nodes[children_id as usize];
432 let left_child = &children.left;
433 let right_child = &children.right;
434
435 let right_is_leaf = right.is_leaf();
436 let left_child_is_leaf = left_child.is_leaf();
437 let right_child_is_leaf = right_child.is_leaf();
438
439 let right_leaf_data = right.children;
440 let left_child_leaf_data = left_child.children;
441 let right_child_leaf_data = right_child.children;
442
443 self.parents[children_id as usize] = BvhNodeIndex::right(node_id);
444
445 if left_child_moves_up0 {
446 // The left child moves into `left`, and `right` takes it place.
447 self.nodes[node_id as usize].left = *left_child;
448 self.nodes[children_id as usize].left = *right;
449 self.nodes[node_id as usize].right =
450 self.nodes[children_id as usize].merged(children_id);
451
452 if left_child_is_leaf {
453 self.leaf_node_indices[left_child_leaf_data as usize] =
454 BvhNodeIndex::left(node_id);
455 } else {
456 self.parents[left_child_leaf_data as usize] = BvhNodeIndex::left(node_id);
457 }
458 if right_is_leaf {
459 self.leaf_node_indices[right_leaf_data as usize] =
460 BvhNodeIndex::left(children_id);
461 } else {
462 self.parents[right_leaf_data as usize] = BvhNodeIndex::left(children_id);
463 }
464 } else {
465 // The right child moves into `left`, and `right` takes it place.
466 self.nodes[node_id as usize].left = *right_child;
467 self.nodes[children_id as usize].right = *right;
468 self.nodes[node_id as usize].right =
469 self.nodes[children_id as usize].merged(children_id);
470 if right_child_is_leaf {
471 self.leaf_node_indices[right_child_leaf_data as usize] =
472 BvhNodeIndex::left(node_id);
473 } else {
474 self.parents[right_child_leaf_data as usize] = BvhNodeIndex::left(node_id);
475 }
476 if right_is_leaf {
477 self.leaf_node_indices[right_leaf_data as usize] =
478 BvhNodeIndex::right(children_id);
479 } else {
480 self.parents[right_leaf_data as usize] = BvhNodeIndex::right(children_id);
481 }
482 }
483 } else {
484 // Apply LEFT rotation.
485 let children_id = right.children;
486 let children = self.nodes[children_id as usize];
487 let left_child = &children.left;
488 let right_child = &children.right;
489
490 let left_is_leaf = left.is_leaf();
491 let left_child_is_leaf = left_child.is_leaf();
492 let right_child_is_leaf = right_child.is_leaf();
493
494 let left_leaf_data = left.children;
495 let left_child_leaf_data = left_child.children;
496 let right_child_leaf_data = right_child.children;
497
498 self.parents[children_id as usize] = BvhNodeIndex::left(node_id);
499
500 if left_child_moves_up1 {
501 // The left child moves into `right`, and `left` takes it place.
502 self.nodes[node_id as usize].right = *left_child;
503 self.nodes[children_id as usize].left = *left;
504 self.nodes[node_id as usize].left =
505 self.nodes[children_id as usize].merged(children_id);
506 if left_child_is_leaf {
507 self.leaf_node_indices[left_child_leaf_data as usize] =
508 BvhNodeIndex::right(node_id);
509 } else {
510 self.parents[left_child_leaf_data as usize] = BvhNodeIndex::right(node_id);
511 }
512 if left_is_leaf {
513 self.leaf_node_indices[left_leaf_data as usize] =
514 BvhNodeIndex::left(children_id);
515 } else {
516 self.parents[left_leaf_data as usize] = BvhNodeIndex::left(children_id);
517 }
518 } else {
519 // The right child moves into `right`, and `left` takes it place.
520 self.nodes[node_id as usize].right = *right_child;
521 self.nodes[children_id as usize].right = *left;
522 self.nodes[node_id as usize].left =
523 self.nodes[children_id as usize].merged(children_id);
524 if right_child_is_leaf {
525 set_leaf_data!(right_child_leaf_data, node_id, BvhNodeIndex::RIGHT);
526 } else {
527 self.parents[right_child_leaf_data as usize] = BvhNodeIndex::right(node_id);
528 }
529 if left_is_leaf {
530 set_leaf_data!(left_leaf_data, children_id, BvhNodeIndex::RIGHT);
531 } else {
532 self.parents[left_leaf_data as usize] = BvhNodeIndex::right(children_id);
533 }
534 }
535 }
536 }
537 }
538}