Skip to main content

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#[cfg(feature = "parallel")]
8use alloc::vec::Vec;
9
10/// Result of a leaf update through [`Bvh::insert_or_update_partially`] or
11/// [`Bvh::insert_with_change_detection`].
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum BvhLeafUpdateStatus {
14    /// The leaf already existed and its stored (fattened) AABB still contains the new
15    /// AABB: the tree was left completely untouched.
16    Unchanged,
17    /// The leaf already existed and its stored AABB was rewritten in place (with
18    /// [`Bvh::insert_or_update_partially`], its ancestors might no longer enclose it
19    /// until the next refit).
20    UpdatedInPlace,
21    /// The leaf didn't exist yet and was inserted (the tree topology changed).
22    Inserted,
23}
24
25impl Bvh {
26    /// Inserts a new leaf into the BVH or updates an existing one.
27    ///
28    /// If a leaf with the given `leaf_index` already exists in the tree, its AABB is updated
29    /// to the new value and the tree's internal nodes are adjusted to maintain correctness.
30    /// If the leaf doesn't exist, it's inserted into an optimal position based on the
31    /// Surface Area Heuristic (SAH).
32    ///
33    /// This operation automatically propagates AABB changes up the tree to maintain the
34    /// invariant that each internal node's AABB encloses all its descendants. For better
35    /// performance when updating many leaves, consider using [`insert_or_update_partially`]
36    /// followed by a single [`refit`] call.
37    ///
38    /// # Arguments
39    ///
40    /// * `aabb` - The Axis-Aligned Bounding Box for this leaf
41    /// * `leaf_index` - A unique identifier for this leaf (typically an object ID)
42    ///
43    /// # Performance
44    ///
45    /// - **Insert new leaf**: O(log n) average, O(n) worst case
46    /// - **Update existing leaf**: O(log n) for propagation up the tree
47    ///
48    /// # Examples
49    ///
50    /// ## Adding new objects
51    ///
52    /// ```
53    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
54    /// use parry3d::partitioning::Bvh;
55    /// use parry3d::bounding_volume::Aabb;
56    /// use parry3d::math::Vector;
57    ///
58    /// let mut bvh = Bvh::new();
59    ///
60    /// // Insert objects with custom IDs
61    /// bvh.insert(Aabb::new(Vector::ZERO, Vector::new(1.0, 1.0, 1.0)), 100);
62    /// bvh.insert(Aabb::new(Vector::new(5.0, 0.0, 0.0), Vector::new(6.0, 1.0, 1.0)), 200);
63    /// bvh.insert(Aabb::new(Vector::new(10.0, 0.0, 0.0), Vector::new(11.0, 1.0, 1.0)), 300);
64    ///
65    /// assert_eq!(bvh.leaf_count(), 3);
66    /// # }
67    /// ```
68    ///
69    /// ## Updating object positions
70    ///
71    /// ```
72    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
73    /// use parry3d::partitioning::Bvh;
74    /// use parry3d::bounding_volume::Aabb;
75    /// use parry3d::math::Vector;
76    ///
77    /// let mut bvh = Bvh::new();
78    ///
79    /// // Insert an object
80    /// bvh.insert(Aabb::new(Vector::ZERO, Vector::new(1.0, 1.0, 1.0)), 42);
81    ///
82    /// // Simulate the object moving - just insert with the same ID
83    /// bvh.insert(Aabb::new(Vector::new(5.0, 0.0, 0.0), Vector::new(6.0, 1.0, 1.0)), 42);
84    ///
85    /// // The BVH still has only 1 leaf, but at the new position
86    /// assert_eq!(bvh.leaf_count(), 1);
87    /// # }
88    /// ```
89    ///
90    /// ## Bulk updates with better performance
91    ///
92    /// ```
93    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
94    /// use parry3d::partitioning::{Bvh, BvhWorkspace};
95    /// use parry3d::bounding_volume::Aabb;
96    /// use parry3d::math::Vector;
97    ///
98    /// let mut bvh = Bvh::new();
99    /// let mut workspace = BvhWorkspace::default();
100    ///
101    /// // Add initial objects
102    /// for i in 0..100 {
103    ///     let aabb = Aabb::new(
104    ///         Vector::new(i as f32, 0.0, 0.0),
105    ///         Vector::new(i as f32 + 1.0, 1.0, 1.0)
106    ///     );
107    ///     bvh.insert(aabb, i);
108    /// }
109    ///
110    /// // For better performance on bulk updates, use insert_or_update_partially
111    /// // then refit once at the end
112    /// for i in 0..100 {
113    ///     let aabb = Aabb::new(
114    ///         Vector::new(i as f32 + 0.1, 0.0, 0.0),
115    ///         Vector::new(i as f32 + 1.1, 1.0, 1.0)
116    ///     );
117    ///     bvh.insert_or_update_partially(aabb, i, 0.0);
118    /// }
119    /// bvh.refit(&mut workspace); // Update tree in one pass
120    /// # }
121    /// ```
122    ///
123    /// # Notes
124    ///
125    /// - Leaf indices can be any `u32` value - they don't need to be contiguous
126    /// - The same leaf index can only exist once in the tree
127    /// - For dynamic scenes, call this every frame for moving objects
128    /// - Consider calling [`refit`] or [`optimize_incremental`] periodically for best
129    ///   query performance
130    ///
131    /// # See Also
132    ///
133    /// - [`insert_with_change_detection`](Self::insert_with_change_detection) - Insert with
134    ///   margin for motion prediction
135    /// - [`insert_or_update_partially`](Self::insert_or_update_partially) - Insert without
136    ///   propagation (faster for bulk updates)
137    /// - [`remove`](Self::remove) - Remove a leaf from the tree
138    /// - [`refit`](Self::refit) - Update tree after bulk modifications
139    ///
140    /// [`insert_or_update_partially`]: Self::insert_or_update_partially
141    /// [`refit`]: Self::refit
142    /// [`optimize_incremental`]: Self::optimize_incremental
143    pub fn insert(&mut self, aabb: Aabb, leaf_index: u32) {
144        let _ = self.insert_with_change_detection(aabb, leaf_index, 0.0);
145    }
146
147    /// Inserts a leaf into this BVH, or updates it if already exists.
148    ///
149    /// If the `aabb` is already contained by the existing leaf node AABB, nothing is modified.
150    /// Otherwise, the aabb being effectively inserted is equal to `aabb` enlarged by the
151    /// `change_detection_margin`.
152    pub fn insert_with_change_detection(
153        &mut self,
154        aabb: Aabb,
155        leaf_index: u32,
156        change_detection_margin: Real,
157    ) -> BvhLeafUpdateStatus {
158        if let Some(leaf) = self.leaf_node_indices.get(leaf_index as usize) {
159            let node = &mut self.nodes[*leaf];
160
161            if change_detection_margin > 0.0 {
162                if !node.contains_aabb(&aabb) {
163                    node.mins = aabb.mins - Vector::splat(change_detection_margin);
164                    node.maxs = aabb.maxs + Vector::splat(change_detection_margin);
165                    node.data.set_change_pending();
166                } else {
167                    // No change detected, no propagation needed.
168                    return BvhLeafUpdateStatus::Unchanged;
169                }
170            } else {
171                node.mins = aabb.mins;
172                node.maxs = aabb.maxs;
173            }
174
175            // Propagate up.
176            // TODO: maybe we should offer multiple propagation strategy.
177            //       The one we currently implement simply stops as soon as a
178            //       parent node contains the given `aabb`, but it won’t try
179            //       to make the parent AABBs smaller even if we could.
180            //       There could be two additional strategies that are slower but would leave the
181            //       tree in a tighter state:
182            //       - Make the parent smaller if possible by merging the aabb with
183            //         the sibling.
184            //       - In addition to merging with the sibling, we could apply bottom-up
185            //         tree rotations to optimize part of the tree on our way up to the
186            //         root.
187            let wide_node_id = leaf.decompose().0;
188            if wide_node_id == 0 {
189                // Already at the root, no propagation possible.
190                return BvhLeafUpdateStatus::UpdatedInPlace;
191            }
192
193            let mut parent = self.parents[wide_node_id];
194            loop {
195                let node = &mut self.nodes[parent];
196                if node.contains_aabb(&aabb) {
197                    // No more propagation needed, the parent is big enough.
198                    break;
199                }
200
201                node.mins = node.mins.min(aabb.mins);
202                node.maxs = node.maxs.max(aabb.maxs);
203
204                let wide_node_id = parent.decompose().0;
205                if wide_node_id == 0 {
206                    break;
207                }
208
209                parent = self.parents[wide_node_id];
210            }
211
212            BvhLeafUpdateStatus::UpdatedInPlace
213        } else {
214            self.insert_new_unchecked(aabb, leaf_index);
215            BvhLeafUpdateStatus::Inserted
216        }
217    }
218
219    /// Either inserts a node on this tree, or, if it already exists, updates its associated bounding
220    /// but doesn’t update its ascendant nodes.
221    ///
222    /// This method is primarily designed to be called for inserting new nodes or updating existing
223    /// ones, and then running a [`Bvh::refit`]. Until [`Bvh::refit`] or [`Bvh::refit_without_opt`]
224    /// is called, the BVH will effectively be left in an invalid state where some internal nodes
225    /// might no longer enclose their children.
226    ///
227    /// For an alternative that inserts a node while also making sure all its ascendants are
228    /// up to date, see [`Bvh::insert`].
229    pub fn insert_or_update_partially(
230        &mut self,
231        aabb: Aabb,
232        leaf_index: u32,
233        change_detection_margin: Real,
234    ) -> BvhLeafUpdateStatus {
235        match self.update_partially_if_present(aabb, leaf_index, change_detection_margin) {
236            Some(status) => status,
237            None => {
238                self.insert_new_unchecked(aabb, leaf_index);
239                BvhLeafUpdateStatus::Inserted
240            }
241        }
242    }
243
244    /// [`Self::insert_or_update_partially`] restricted to leaves already in the tree:
245    /// returns `None`, leaving the tree untouched, when `leaf_index` has no leaf yet.
246    ///
247    /// Lets a caller apply every in-place update before any structural insertion — the
248    /// order `insert_or_update_batch_partially_parallel` imposes, since its updates
249    /// run concurrently — without paying a second lookup to find out which updates are
250    /// insertions.
251    pub fn update_partially_if_present(
252        &mut self,
253        aabb: Aabb,
254        leaf_index: u32,
255        change_detection_margin: Real,
256    ) -> Option<BvhLeafUpdateStatus> {
257        let leaf = *self.leaf_node_indices.get(leaf_index as usize)?;
258        let node = &mut self.nodes[leaf];
259
260        if change_detection_margin > 0.0 {
261            if !node.contains_aabb(&aabb) {
262                node.mins = aabb.mins - Vector::splat(change_detection_margin);
263                node.maxs = aabb.maxs + Vector::splat(change_detection_margin);
264                node.data.set_change_pending();
265                Some(BvhLeafUpdateStatus::UpdatedInPlace)
266            } else {
267                // The new AABB is still inside the leaf's fat AABB: the tree
268                // is left untouched.
269                Some(BvhLeafUpdateStatus::Unchanged)
270            }
271        } else {
272            node.mins = aabb.mins;
273            node.maxs = aabb.maxs;
274            Some(BvhLeafUpdateStatus::UpdatedInPlace)
275        }
276    }
277
278    /// Batch, parallel version of [`Self::insert_or_update_partially`].
279    ///
280    /// Applies every update whose leaf already exists in parallel (existing
281    /// leaves are updated in place: distinct leaf indices map to distinct
282    /// [`BvhNode`]s, so the writes are disjoint — two sibling leaves share a
283    /// wide node but occupy its two disjoint halves), then inserts the new
284    /// leaves sequentially. `statuses` is filled with one entry per update, in
285    /// order.
286    ///
287    /// Like [`Self::insert_or_update_partially`], the ascendants of updated
288    /// leaves are **not** updated: the tree must be refitted (e.g.
289    /// [`Bvh::refit`]) before the next query for its results to be exact.
290    ///
291    /// # Panics
292    ///
293    /// May panic (or leave arbitrary leaf AABBs, but no memory unsafety beyond
294    /// a torn AABB) if the same leaf index appears twice in `updates`.
295    #[cfg(feature = "parallel")]
296    pub fn insert_or_update_batch_partially_parallel(
297        &mut self,
298        updates: &[(Aabb, u32, Real)],
299        statuses: &mut Vec<BvhLeafUpdateStatus>,
300    ) {
301        use rayon::prelude::*;
302
303        statuses.clear();
304        statuses.resize(updates.len(), BvhLeafUpdateStatus::Unchanged);
305
306        // Phase 1 (parallel): in-place updates of the existing leaves.
307        struct NodesPtr(*mut BvhNodeWide);
308        unsafe impl Sync for NodesPtr {}
309        let nodes = &NodesPtr(self.nodes.0.as_mut_ptr());
310        let leaf_node_indices = &self.leaf_node_indices;
311
312        updates.par_iter().zip(statuses.par_iter_mut()).for_each(
313            move |((aabb, leaf_index, change_detection_margin), status)| {
314                let Some(leaf) = leaf_node_indices.get(*leaf_index as usize) else {
315                    // Structural change: deferred to the sequential phase below.
316                    *status = BvhLeafUpdateStatus::Inserted;
317                    return;
318                };
319                let (wide_id, is_right) = leaf.decompose();
320                // SAFETY: distinct leaf indices map to distinct (wide, side)
321                //         slots, i.e. disjoint `BvhNode`s; `leaf_node_indices`
322                //         is only read during this phase.
323                let wide = unsafe { &mut *nodes.0.add(wide_id) };
324                let node = if is_right {
325                    &mut wide.right
326                } else {
327                    &mut wide.left
328                };
329
330                if *change_detection_margin > 0.0 {
331                    if !node.contains_aabb(aabb) {
332                        node.mins = aabb.mins - Vector::splat(*change_detection_margin);
333                        node.maxs = aabb.maxs + Vector::splat(*change_detection_margin);
334                        node.data.set_change_pending();
335                        *status = BvhLeafUpdateStatus::UpdatedInPlace;
336                    } else {
337                        // The new AABB is still inside the leaf's fat AABB: the
338                        // tree is left untouched.
339                        *status = BvhLeafUpdateStatus::Unchanged;
340                    }
341                } else {
342                    node.mins = aabb.mins;
343                    node.maxs = aabb.maxs;
344                    *status = BvhLeafUpdateStatus::UpdatedInPlace;
345                }
346            },
347        );
348
349        // Phase 2 (sequential): structural insertions.
350        for (update, status) in updates.iter().zip(statuses.iter()) {
351            if *status == BvhLeafUpdateStatus::Inserted {
352                self.insert_new_unchecked(update.0, update.1);
353            }
354        }
355    }
356
357    /// Inserts a new leaf into this BVH without checking if it already exists.
358    fn insert_new_unchecked(&mut self, aabb: Aabb, leaf_index: u32) {
359        let _ = self
360            .leaf_node_indices
361            .insert(leaf_index as usize, BvhNodeIndex::default());
362        let leaf_index_mut = &mut self.leaf_node_indices[leaf_index as usize];
363
364        // If the tree is empty, create the root.
365        if self.nodes.is_empty() {
366            self.nodes.push(BvhNodeWide {
367                left: BvhNode::leaf(aabb, leaf_index),
368                right: BvhNode::zeros(),
369            });
370            self.parents.push(BvhNodeIndex::default());
371            *leaf_index_mut = BvhNodeIndex::left(0);
372            return;
373        }
374
375        // If we have a root, but it is partial, just complete it.
376        if self.nodes[0].right.leaf_count() == 0 {
377            self.nodes[0].right = BvhNode::leaf(aabb, leaf_index);
378            *leaf_index_mut = BvhNodeIndex::right(0);
379            return;
380        }
381
382        // General case: traverse the tree to find room for the new leaf.
383        let mut curr_id = 0u32;
384        let mut path_taken = vec![];
385
386        const APPLY_ROTATIONS_DOWN: bool = true;
387        const APPLY_ROTATIONS_UP: bool = false;
388
389        loop {
390            if APPLY_ROTATIONS_UP {
391                path_taken.push(curr_id);
392            }
393
394            if APPLY_ROTATIONS_DOWN {
395                self.maybe_apply_rotation(curr_id);
396            }
397
398            let curr_node = &self.nodes[curr_id as usize];
399
400            // Need to determine the best side to insert our node.
401            let left = &curr_node.left;
402            let right = &curr_node.right;
403
404            let left_merged_aabb = left.aabb().merged(&aabb);
405            let right_merged_aabb = right.aabb().merged(&aabb);
406
407            let left_merged_vol = left_merged_aabb.volume();
408            let right_merged_vol = right_merged_aabb.volume();
409            let left_vol = left.aabb().volume();
410            let right_vol = right.aabb().volume();
411            let left_count = left.leaf_count();
412            let right_count = right.leaf_count();
413
414            // NOTE: when calculating the SAH cost, we don’t care about dividing by the
415            //       parent’s volume since both compared costs use the same factor so
416            //       ignoring it doesn’t affect the comparison.
417            let left_cost =
418                left_merged_vol * (left_count + 1) as Real + right_vol * right_count as Real;
419            let right_cost =
420                right_merged_vol * (right_count + 1) as Real + left_vol * left_count as Real;
421
422            // Insert into the branch with lowest post-insertion SAH cost.
423            // If the costs are equal, just pick the branch with the smallest leaf count.
424            if left_cost < right_cost || (left_cost == right_cost && left_count < right_count) {
425                // Insert left. The `left` node will become an internal node.
426                // We create a new wide leaf containing the current and new leaves and
427                // attach it to `left`.
428                if left.is_leaf() {
429                    let wide_node = BvhNodeWide {
430                        left: *left,
431                        right: BvhNode::leaf(aabb, leaf_index),
432                    };
433                    let new_leaf_id = self.alloc_wide_node(wide_node, BvhNodeIndex::left(curr_id));
434
435                    let left = &mut self.nodes[curr_id as usize].left;
436                    self.leaf_node_indices[left.children as usize] =
437                        BvhNodeIndex::left(new_leaf_id as u32);
438                    self.leaf_node_indices[leaf_index as usize] =
439                        BvhNodeIndex::right(new_leaf_id as u32);
440
441                    left.children = new_leaf_id as u32;
442                    left.data.add_leaf_count(1);
443                    left.mins = left.mins.min(aabb.mins);
444                    left.maxs = left.maxs.max(aabb.maxs);
445                    break;
446                } else {
447                    let left = &mut self.nodes[curr_id as usize].left;
448                    curr_id = left.children;
449                    left.data.add_leaf_count(1);
450                    left.mins = left.mins.min(aabb.mins);
451                    left.maxs = left.maxs.max(aabb.maxs);
452                }
453            } else {
454                // Insert right. The `right` node will become an internal node.
455                // We create a new wide leaf containing the current and new leaves and
456                // attach it to `right`.
457                if right.is_leaf() {
458                    let new_node = BvhNodeWide {
459                        left: BvhNode::leaf(aabb, leaf_index),
460                        right: *right,
461                    };
462                    let new_leaf_id = self.alloc_wide_node(new_node, BvhNodeIndex::right(curr_id));
463
464                    let right = &mut self.nodes[curr_id as usize].right;
465                    self.leaf_node_indices[leaf_index as usize] =
466                        BvhNodeIndex::left(new_leaf_id as u32);
467                    self.leaf_node_indices[right.children as usize] =
468                        BvhNodeIndex::right(new_leaf_id as u32);
469
470                    right.children = new_leaf_id as u32;
471                    right.data.add_leaf_count(1);
472                    right.mins = right.mins.min(aabb.mins);
473                    right.maxs = right.maxs.max(aabb.maxs);
474                    break;
475                } else {
476                    let right = &mut self.nodes[curr_id as usize].right;
477                    curr_id = right.children;
478                    right.data.add_leaf_count(1);
479                    right.mins = right.mins.min(aabb.mins);
480                    right.maxs = right.maxs.max(aabb.maxs);
481                }
482            }
483        }
484
485        if APPLY_ROTATIONS_UP {
486            while let Some(node) = path_taken.pop() {
487                self.maybe_apply_rotation(node);
488            }
489        }
490    }
491
492    /// Allocates a slot for a new wide node, preferring slots orphaned by earlier
493    /// leaf removals over growing the node array.
494    fn alloc_wide_node(&mut self, node: BvhNodeWide, parent: BvhNodeIndex) -> usize {
495        if let Some(free) = self.free_wide_nodes.pop() {
496            self.nodes[free as usize] = node;
497            self.parents[free as usize] = parent;
498            free as usize
499        } else {
500            let id = self.nodes.len();
501            self.nodes.push(node);
502            self.parents.push(parent);
503            id
504        }
505    }
506
507    /// Updates the leaf AABB like [`Self::insert_with_change_detection`], but
508    /// relocates the leaf through a removal + SAH re-insertion whenever its fattened
509    /// AABB must actually change.
510    ///
511    /// Unlike the in-place update of [`Self::insert_with_change_detection`] (which
512    /// keeps the leaf's tree position and only enlarges its ancestors), the
513    /// re-insertion picks a fresh position by SAH descent — including rotations —
514    /// so a stream of such updates keeps the tree quality high on its own, without
515    /// requiring periodic [`Self::optimize_incremental`] passes. The freed wide-node
516    /// slot is recycled by the re-insertion itself, so the node array doesn't grow.
517    ///
518    /// This is the preferred update path when only a small fraction of the leaves
519    /// move (the per-leaf cost is an O(log n) descent instead of O(1)); for bulk
520    /// updates, prefer in-place updates followed by a refit and periodic
521    /// optimization.
522    ///
523    /// Compatible with [`Self::refit_partial`] under the same rules as insertions.
524    pub fn reinsert_or_update_with_change_detection(
525        &mut self,
526        aabb: Aabb,
527        leaf_index: u32,
528        change_detection_margin: Real,
529    ) -> BvhLeafUpdateStatus {
530        match self.reinsert_or_update_if_present(aabb, leaf_index, change_detection_margin) {
531            Some(status) => status,
532            None => {
533                self.insert_new_unchecked(aabb, leaf_index);
534                BvhLeafUpdateStatus::Inserted
535            }
536        }
537    }
538
539    /// [`Self::reinsert_or_update_with_change_detection`] restricted to leaves already in
540    /// the tree: returns `None`, leaving the tree untouched, when `leaf_index` has no leaf
541    /// yet. See [`Self::update_partially_if_present`].
542    pub fn reinsert_or_update_if_present(
543        &mut self,
544        aabb: Aabb,
545        leaf_index: u32,
546        change_detection_margin: Real,
547    ) -> Option<BvhLeafUpdateStatus> {
548        let leaf = *self.leaf_node_indices.get(leaf_index as usize)?;
549        if self.nodes[leaf].contains_aabb(&aabb) {
550            return Some(BvhLeafUpdateStatus::Unchanged);
551        }
552
553        self.remove(leaf_index);
554        let fat_aabb = Aabb {
555            mins: aabb.mins - Vector::splat(change_detection_margin),
556            maxs: aabb.maxs + Vector::splat(change_detection_margin),
557        };
558        // The new leaf is created with a pending change flag, exactly like an
559        // in-place update that escaped its previous fattened AABB.
560        self.insert_new_unchecked(fat_aabb, leaf_index);
561        Some(BvhLeafUpdateStatus::UpdatedInPlace)
562    }
563
564    // Applies a tree rotation at the given `node` if this improves the SAH metric at that node.
565    fn maybe_apply_rotation(&mut self, node_id: u32) {
566        let node = self.nodes[node_id as usize];
567        let left = &node.left;
568        let right = &node.right;
569
570        let curr_score =
571            left.volume() * left.leaf_count() as Real + right.volume() * right.leaf_count() as Real;
572
573        macro_rules! eval_costs {
574            ($left: ident, $right: ident) => {
575                if !$left.is_leaf() {
576                    let children = self.nodes[$left.children as usize];
577                    let left_child = &children.left;
578                    let right_child = &children.right;
579
580                    // New SAH score after transforming [{left_child, right_child}, right]
581                    // into [left_child, {right_child, right}].
582                    let new_score1 = left_child.volume() * left_child.leaf_count() as Real
583                        + right_child.merged_volume($right)
584                            * (right_child.leaf_count() + $right.leaf_count()) as Real;
585
586                    // New SAH score after transforming [{left_child, right_child}, right]
587                    // into [right_child, {left_child, right}].
588                    let new_score2 = right_child.volume() * right_child.leaf_count() as Real
589                        + left_child.merged_volume($right)
590                            * (left_child.leaf_count() + $right.leaf_count()) as Real;
591
592                    if new_score1 < new_score2 {
593                        (new_score1 - curr_score, true)
594                    } else {
595                        (new_score2 - curr_score, false)
596                    }
597                } else {
598                    (Real::MAX, false)
599                }
600            };
601        }
602
603        // Because of the rotation some leaves might have changed location.
604        // This a helper to update the `leaf_data` map accordingly.
605        macro_rules! set_leaf_data {
606            ($leaf_data_id: ident, $node_id: ident, $left_or_right: expr) => {
607                self.leaf_node_indices[$leaf_data_id as usize] =
608                    BvhNodeIndex::new($node_id, $left_or_right);
609            };
610        }
611
612        // For right rotation.
613        let (rotation_score0, left_child_moves_up0) = eval_costs!(left, right);
614        // For left rotation.
615        let (rotation_score1, left_child_moves_up1) = eval_costs!(right, left);
616
617        if rotation_score0 < 0.0 || rotation_score1 < 0.0 {
618            // At least one of the rotations is worth it, apply the one with
619            // the best impact on SAH scoring.
620            if rotation_score0 < rotation_score1 {
621                // Apply RIGHT rotation.
622                let children_id = left.children;
623                let children = self.nodes[children_id as usize];
624                let left_child = &children.left;
625                let right_child = &children.right;
626
627                let right_is_leaf = right.is_leaf();
628                let left_child_is_leaf = left_child.is_leaf();
629                let right_child_is_leaf = right_child.is_leaf();
630
631                let right_leaf_data = right.children;
632                let left_child_leaf_data = left_child.children;
633                let right_child_leaf_data = right_child.children;
634
635                self.parents[children_id as usize] = BvhNodeIndex::right(node_id);
636
637                if left_child_moves_up0 {
638                    // The left child moves into `left`, and `right` takes it place.
639                    self.nodes[node_id as usize].left = *left_child;
640                    self.nodes[children_id as usize].left = *right;
641                    self.nodes[node_id as usize].right =
642                        self.nodes[children_id as usize].merged(children_id);
643
644                    if left_child_is_leaf {
645                        self.leaf_node_indices[left_child_leaf_data as usize] =
646                            BvhNodeIndex::left(node_id);
647                    } else {
648                        self.parents[left_child_leaf_data as usize] = BvhNodeIndex::left(node_id);
649                    }
650                    if right_is_leaf {
651                        self.leaf_node_indices[right_leaf_data as usize] =
652                            BvhNodeIndex::left(children_id);
653                    } else {
654                        self.parents[right_leaf_data as usize] = BvhNodeIndex::left(children_id);
655                    }
656                } else {
657                    // The right child moves into `left`, and `right` takes it place.
658                    self.nodes[node_id as usize].left = *right_child;
659                    self.nodes[children_id as usize].right = *right;
660                    self.nodes[node_id as usize].right =
661                        self.nodes[children_id as usize].merged(children_id);
662                    if right_child_is_leaf {
663                        self.leaf_node_indices[right_child_leaf_data as usize] =
664                            BvhNodeIndex::left(node_id);
665                    } else {
666                        self.parents[right_child_leaf_data as usize] = BvhNodeIndex::left(node_id);
667                    }
668                    if right_is_leaf {
669                        self.leaf_node_indices[right_leaf_data as usize] =
670                            BvhNodeIndex::right(children_id);
671                    } else {
672                        self.parents[right_leaf_data as usize] = BvhNodeIndex::right(children_id);
673                    }
674                }
675            } else {
676                // Apply LEFT rotation.
677                let children_id = right.children;
678                let children = self.nodes[children_id as usize];
679                let left_child = &children.left;
680                let right_child = &children.right;
681
682                let left_is_leaf = left.is_leaf();
683                let left_child_is_leaf = left_child.is_leaf();
684                let right_child_is_leaf = right_child.is_leaf();
685
686                let left_leaf_data = left.children;
687                let left_child_leaf_data = left_child.children;
688                let right_child_leaf_data = right_child.children;
689
690                self.parents[children_id as usize] = BvhNodeIndex::left(node_id);
691
692                if left_child_moves_up1 {
693                    // The left child moves into `right`, and `left` takes it place.
694                    self.nodes[node_id as usize].right = *left_child;
695                    self.nodes[children_id as usize].left = *left;
696                    self.nodes[node_id as usize].left =
697                        self.nodes[children_id as usize].merged(children_id);
698                    if left_child_is_leaf {
699                        self.leaf_node_indices[left_child_leaf_data as usize] =
700                            BvhNodeIndex::right(node_id);
701                    } else {
702                        self.parents[left_child_leaf_data as usize] = BvhNodeIndex::right(node_id);
703                    }
704                    if left_is_leaf {
705                        self.leaf_node_indices[left_leaf_data as usize] =
706                            BvhNodeIndex::left(children_id);
707                    } else {
708                        self.parents[left_leaf_data as usize] = BvhNodeIndex::left(children_id);
709                    }
710                } else {
711                    // The right child moves into `right`, and `left` takes it place.
712                    self.nodes[node_id as usize].right = *right_child;
713                    self.nodes[children_id as usize].right = *left;
714                    self.nodes[node_id as usize].left =
715                        self.nodes[children_id as usize].merged(children_id);
716                    if right_child_is_leaf {
717                        set_leaf_data!(right_child_leaf_data, node_id, BvhNodeIndex::RIGHT);
718                    } else {
719                        self.parents[right_child_leaf_data as usize] = BvhNodeIndex::right(node_id);
720                    }
721                    if left_is_leaf {
722                        set_leaf_data!(left_leaf_data, children_id, BvhNodeIndex::RIGHT);
723                    } else {
724                        self.parents[left_leaf_data as usize] = BvhNodeIndex::right(children_id);
725                    }
726                }
727            }
728        }
729    }
730}