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}