parry3d_f64/partitioning/qbvh/
update.rs

1use crate::bounding_volume::{Aabb, BoundingVolume, SimdAabb};
2#[cfg(feature = "dim3")]
3use crate::math::Vector;
4use crate::math::{Point, Real};
5use crate::partitioning::{CenterDataSplitter, QbvhProxy};
6use crate::simd::{SimdReal, SIMD_WIDTH};
7use alloc::{vec, vec::Vec};
8use simba::simd::{SimdBool, SimdValue};
9
10use super::{IndexedData, NodeIndex, Qbvh, QbvhNode, QbvhNodeFlags};
11
12#[cfg(test)]
13mod tests;
14
15#[derive(Clone, Default)]
16/// Workspace for QBVH update.
17///
18/// Re-using the same workspace for multiple QBVH modification isn’t mandatory, but it improves
19/// performances by avoiding useless allocation.
20pub struct QbvhUpdateWorkspace {
21    stack: Vec<(u32, u8)>,
22    dirty_parent_nodes: Vec<u32>,
23    // For rebalancing.
24    to_sort: Vec<usize>,
25    orig_ids: Vec<u32>,
26    is_leaf: Vec<bool>,
27    aabbs: Vec<Aabb>,
28}
29
30impl QbvhUpdateWorkspace {
31    fn clear(&mut self) {
32        self.stack.clear();
33        self.dirty_parent_nodes.clear();
34        self.to_sort.clear();
35        self.orig_ids.clear();
36        self.is_leaf.clear();
37        self.aabbs.clear();
38    }
39}
40
41impl<LeafData: IndexedData> Qbvh<LeafData> {
42    /// Immediately remove a leaf from this QBVH.
43    pub fn remove(&mut self, data: LeafData) -> Option<LeafData> {
44        let id = data.index();
45        let proxy = self.proxies.get_mut(id)?;
46        let node = self.nodes.get_mut(proxy.node.index as usize)?;
47        node.children[proxy.node.lane as usize] = u32::MAX;
48
49        if !node.is_dirty() {
50            node.set_dirty(true);
51            self.dirty_nodes.push(proxy.node.index);
52        }
53
54        *proxy = QbvhProxy::invalid();
55
56        Some(data)
57    }
58
59    /// Prepare a new leaf for insertion into this QBVH (or for update if it already exists).
60    ///
61    /// The insertion or update will be completely valid only after the next call to [`Qbvh::refit`].
62    pub fn pre_update_or_insert(&mut self, data: LeafData) {
63        if self.nodes.is_empty() {
64            let mut root = QbvhNode::empty();
65            root.children = [1, u32::MAX, u32::MAX, u32::MAX];
66            self.nodes
67                .extend_from_slice(&[root, QbvhNode::empty_leaf_with_parent(NodeIndex::new(0, 0))]);
68        }
69
70        let proxy_id = data.index();
71
72        if self.proxies.len() <= proxy_id {
73            self.proxies.resize(proxy_id + 1, QbvhProxy::invalid());
74        }
75
76        let proxy = &mut self.proxies[proxy_id];
77        proxy.data = data;
78
79        if proxy.is_detached() {
80            // Attach the proxy into one of the leaf attached to the root, if there is room.
81            for ii in 0..SIMD_WIDTH {
82                let mut child = self.nodes[0].children[ii];
83
84                if child == u32::MAX {
85                    // Missing node, create it.
86                    child = self.nodes.len() as u32;
87                    self.nodes
88                        .push(QbvhNode::empty_leaf_with_parent(NodeIndex::new(
89                            0, ii as u8,
90                        )));
91                    self.nodes[0].children[ii] = child;
92                }
93
94                let child_node = &mut self.nodes[child as usize];
95                // skip non-leaf node that is attached to the root
96                if !child_node.is_leaf() {
97                    continue;
98                }
99
100                // Insert into this node if there is room.
101                for kk in 0..SIMD_WIDTH {
102                    if child_node.children[kk] == u32::MAX {
103                        child_node.children[kk] = proxy_id as u32;
104                        proxy.node = NodeIndex::new(child, kk as u8);
105
106                        if !child_node.is_dirty() {
107                            self.dirty_nodes.push(child);
108                            child_node.set_dirty(true);
109                        }
110
111                        return;
112                    }
113                }
114            }
115
116            // If we reached this point, we didn’t find room for our
117            // new proxy. Create a new root to make more room.
118            let mut old_root = self.nodes[0];
119            old_root.parent = NodeIndex::new(0, 0);
120
121            for child_id in old_root.children {
122                let parent_index = self.nodes.len() as u32;
123
124                if let Some(child_node) = self.nodes.get_mut(child_id as usize) {
125                    child_node.parent.index = parent_index;
126                }
127            }
128
129            let new_leaf_node_id = self.nodes.len() as u32 + 1;
130            let mut new_leaf_node = QbvhNode::empty_leaf_with_parent(NodeIndex::new(0, 1));
131            new_leaf_node.children[0] = proxy_id as u32;
132            // The first new node contains the (unmodified) old root, the second
133            // new node contains the newly added proxy.
134            self.dirty_nodes.push(new_leaf_node_id);
135            new_leaf_node.set_dirty(true);
136            proxy.node = NodeIndex::new(new_leaf_node_id, 0);
137
138            let new_root_children = [
139                self.nodes.len() as u32,
140                new_leaf_node_id,
141                u32::MAX,
142                u32::MAX,
143            ];
144
145            self.nodes.extend_from_slice(&[old_root, new_leaf_node]);
146            self.nodes[0].children = new_root_children;
147        } else {
148            let node = &mut self.nodes[proxy.node.index as usize];
149            if !node.is_dirty() {
150                node.set_dirty(true);
151                self.dirty_nodes.push(proxy.node.index);
152            }
153        }
154    }
155
156    #[allow(dead_code)] // Not sure yet if we want to keep this.
157    pub(crate) fn clear_changed_flag(&mut self, workspace: &mut QbvhUpdateWorkspace) {
158        if self.nodes.is_empty() {
159            return;
160        }
161
162        workspace.clear();
163        workspace.stack.push((0, 0));
164
165        while let Some((id, _)) = workspace.stack.pop() {
166            let node = &mut self.nodes[id as usize];
167
168            if node.is_changed() {
169                node.set_changed(false);
170
171                for child in node.children {
172                    if child < self.nodes.len() as u32 {
173                        workspace.stack.push((child, 0));
174                    }
175                }
176            }
177        }
178    }
179
180    #[doc(hidden)]
181    pub fn check_topology(&self, check_aabbs: bool, aabb_builder: impl Fn(&LeafData) -> Aabb) {
182        if self.nodes.is_empty() {
183            return;
184        }
185
186        let mut stack = vec![];
187        stack.push(0);
188
189        let mut node_id_found = vec![false; self.nodes.len()];
190        let mut proxy_id_found = vec![false; self.proxies.len()];
191
192        while let Some(id) = stack.pop() {
193            let node = &self.nodes[id as usize];
194
195            // No duplicate references to internal nodes.
196            assert!(!node_id_found[id as usize]);
197            node_id_found[id as usize] = true;
198
199            if id != 0 {
200                let parent = &self.nodes[node.parent.index as usize];
201
202                // Check parent <-> children indices.
203                assert!(!parent.is_leaf());
204                assert_eq!(parent.children[node.parent.lane as usize], id);
205
206                if check_aabbs {
207                    // Make sure the parent Aabb contains its child Aabb.
208                    assert!(
209                        parent
210                            .simd_aabb
211                            .extract(node.parent.lane as usize)
212                            .contains(&node.simd_aabb.to_merged_aabb()),
213                        "failed for {id}"
214                    );
215                }
216
217                // If this node is changed, its parent is changed too.
218                if node.is_changed() {
219                    assert!(parent.is_changed());
220                }
221            }
222
223            if node.is_leaf() {
224                for ii in 0..SIMD_WIDTH {
225                    let proxy_id = node.children[ii];
226                    if proxy_id != u32::MAX {
227                        // No duplicate references to proxies.
228                        assert!(!proxy_id_found[proxy_id as usize]);
229                        proxy_id_found[proxy_id as usize] = true;
230
231                        if check_aabbs {
232                            // Proxy Aabb is correct.
233                            let aabb = node.simd_aabb.extract(ii);
234                            assert!(
235                                aabb.contains(&aabb_builder(&self.proxies[proxy_id as usize].data))
236                            );
237                        }
238
239                        // Proxy node reference is correct.
240                        assert_eq!(
241                            self.proxies[proxy_id as usize].node,
242                            NodeIndex::new(id, ii as u8),
243                            "Failed {proxy_id}"
244                        );
245                    }
246                }
247            } else {
248                for child in node.children {
249                    if child != u32::MAX {
250                        stack.push(child);
251                    }
252                }
253            }
254        }
255
256        // Each proxy was visited once.
257        assert_eq!(
258            proxy_id_found.iter().filter(|found| **found).count(),
259            self.proxies.iter().filter(|p| !p.is_detached()).count()
260        );
261    }
262
263    /// Update all the nodes that have been marked as dirty by [`Qbvh::pre_update_or_insert`],
264    /// and [`Qbvh::remove`].
265    ///
266    /// This will not alter the topology of this `Qbvh`.
267    pub fn refit<F>(
268        &mut self,
269        margin: Real,
270        workspace: &mut QbvhUpdateWorkspace,
271        aabb_builder: F,
272    ) -> usize
273    where
274        F: Fn(&LeafData) -> Aabb,
275    {
276        // Loop on the dirty leaves.
277        workspace.clear();
278        let margin = SimdReal::splat(margin);
279        let mut first_iter = true;
280        let mut num_changed = 0;
281
282        while !self.dirty_nodes.is_empty() {
283            while let Some(id) = self.dirty_nodes.pop() {
284                // NOTE: this will cover the case where we reach the root of the tree.
285                if let Some(node) = self.nodes.get(id as usize) {
286                    // Compute the new aabb.
287                    let mut new_aabbs = [Aabb::new_invalid(); SIMD_WIDTH];
288                    for (child_id, new_aabb) in node.children.iter().zip(new_aabbs.iter_mut()) {
289                        if node.is_leaf() {
290                            // We are in a leaf: compute the Aabbs.
291                            if let Some(proxy) = self.proxies.get(*child_id as usize) {
292                                *new_aabb = aabb_builder(&proxy.data);
293                            }
294                        } else {
295                            // We are in an internal node: compute the children's Aabbs.
296                            if let Some(node) = self.nodes.get(*child_id as usize) {
297                                *new_aabb = node.simd_aabb.to_merged_aabb();
298                            }
299                        }
300                    }
301
302                    let node = &mut self.nodes[id as usize];
303                    let new_simd_aabb = SimdAabb::from(new_aabbs);
304                    node.set_dirty(false);
305
306                    if !first_iter || !node.simd_aabb.contains(&new_simd_aabb).all() {
307                        node.set_changed(true);
308                        node.simd_aabb = new_simd_aabb;
309                        node.simd_aabb.loosen(margin);
310                        let parent_id = node.parent.index;
311                        num_changed += 1;
312
313                        if let Some(parent) = self.nodes.get_mut(parent_id as usize) {
314                            if !parent.is_dirty() {
315                                workspace.dirty_parent_nodes.push(parent_id);
316                                parent.set_dirty(true);
317                            }
318                        }
319                    }
320                }
321            }
322
323            first_iter = false;
324            core::mem::swap(&mut self.dirty_nodes, &mut workspace.dirty_parent_nodes);
325        }
326
327        num_changed
328    }
329
330    /// Rebalances the `Qbvh` tree.
331    ///
332    /// This will modify the topology of this tree. This assumes that the leaf AABBs have
333    /// already been updated with [`Qbvh::refit`].
334    pub fn rebalance(&mut self, margin: Real, workspace: &mut QbvhUpdateWorkspace) {
335        if self.nodes.is_empty() {
336            return;
337        }
338
339        workspace.clear();
340
341        const MIN_CHANGED_DEPTH: u8 = 5; // TODO: find a good value
342
343        // PERF: if we have modifications past this depth, the QBVH has become very
344        //       unbalanced and a full rebuild is warranted. Note that for a perfectly
345        //       balanced tree to reach this threshold, it would have to contain
346        //       at least 4^15 = 1.073.741.824 leaves (i.e. very unlikely in most practical
347        //       use-cases).
348        // TODO: work on a way to reduce the risks of imbalance.
349        const FULL_REBUILD_DEPTH: u8 = 15;
350
351        for root_child in self.nodes[0].children {
352            if root_child < self.nodes.len() as u32 {
353                workspace.stack.push((root_child, 1));
354            }
355        }
356
357        // Collect empty slots and pseudo-leaves to sort.
358        let mut force_full_rebuild = false;
359        while let Some((id, depth)) = workspace.stack.pop() {
360            if depth > FULL_REBUILD_DEPTH {
361                force_full_rebuild = true;
362                break;
363            }
364
365            let node = &mut self.nodes[id as usize];
366
367            if node.is_leaf() {
368                self.free_list.push(id);
369                for ii in 0..SIMD_WIDTH {
370                    let proxy_id = node.children[ii];
371                    if proxy_id < self.proxies.len() as u32 {
372                        workspace.orig_ids.push(proxy_id);
373                        workspace.aabbs.push(node.simd_aabb.extract(ii));
374                        workspace.is_leaf.push(true);
375                    }
376                }
377            } else if node.is_changed() || depth < MIN_CHANGED_DEPTH {
378                self.free_list.push(id);
379
380                for child in node.children {
381                    if child < self.nodes.len() as u32 {
382                        workspace.stack.push((child, depth + 1));
383                    }
384                }
385            } else {
386                workspace.orig_ids.push(id);
387                workspace.aabbs.push(node.simd_aabb.to_merged_aabb());
388                workspace.is_leaf.push(false);
389            }
390        }
391
392        if force_full_rebuild {
393            workspace.clear();
394            let all_leaves: Vec<_> = self
395                .proxies
396                .iter()
397                .filter_map(|proxy| self.node_aabb(proxy.node).map(|aabb| (proxy.data, aabb)))
398                .collect();
399
400            self.clear_and_rebuild(all_leaves.into_iter(), 0.0);
401            return;
402        }
403
404        workspace.to_sort.extend(0..workspace.orig_ids.len());
405        let root_id = NodeIndex::new(0, 0);
406
407        let mut indices = core::mem::take(&mut workspace.to_sort);
408        let (id, aabb) = self.do_recurse_rebalance(&mut indices, workspace, root_id, margin);
409        workspace.to_sort = indices;
410
411        self.root_aabb = aabb;
412
413        self.nodes[0] = QbvhNode {
414            simd_aabb: SimdAabb::from([
415                aabb,
416                Aabb::new_invalid(),
417                Aabb::new_invalid(),
418                Aabb::new_invalid(),
419            ]),
420            children: [id, u32::MAX, u32::MAX, u32::MAX],
421            parent: NodeIndex::invalid(),
422            flags: QbvhNodeFlags::default(),
423        };
424    }
425
426    fn do_recurse_rebalance(
427        &mut self,
428        indices: &mut [usize],
429        workspace: &QbvhUpdateWorkspace,
430        parent: NodeIndex,
431        margin: Real,
432    ) -> (u32, Aabb) {
433        if indices.len() <= 4 {
434            // Leaf case.
435            let mut has_leaf = false;
436            let mut has_internal = false;
437
438            for id in &*indices {
439                has_leaf |= workspace.is_leaf[*id];
440                has_internal |= !workspace.is_leaf[*id];
441            }
442
443            let my_internal_id = if has_internal {
444                self.free_list.pop().unwrap_or_else(|| {
445                    self.nodes.push(QbvhNode::empty());
446                    self.nodes.len() as u32 - 1
447                })
448            } else {
449                u32::MAX
450            };
451
452            let my_leaf_id = if has_leaf {
453                self.free_list.pop().unwrap_or_else(|| {
454                    self.nodes.push(QbvhNode::empty());
455                    self.nodes.len() as u32 - 1
456                })
457            } else {
458                u32::MAX
459            };
460
461            let mut my_leaf_aabb = Aabb::new_invalid();
462            let mut my_internal_aabb = Aabb::new_invalid();
463
464            let mut leaf_aabbs = [Aabb::new_invalid(); 4];
465            let mut internal_aabbs = [Aabb::new_invalid(); 4];
466
467            let mut proxy_ids = [u32::MAX; 4];
468            let mut internal_ids = [u32::MAX; 4];
469            let mut new_internal_lane_containing_leaf = usize::MAX;
470
471            for (k, id) in indices.iter().enumerate() {
472                let orig_id = workspace.orig_ids[*id];
473                if workspace.is_leaf[*id] {
474                    new_internal_lane_containing_leaf = k;
475                    my_leaf_aabb.merge(&workspace.aabbs[*id]);
476                    leaf_aabbs[k] = workspace.aabbs[*id];
477                    proxy_ids[k] = orig_id;
478                    self.proxies[orig_id as usize].node = NodeIndex::new(my_leaf_id, k as u8);
479                } else {
480                    my_internal_aabb.merge(&workspace.aabbs[*id]);
481                    internal_aabbs[k] = workspace.aabbs[*id];
482                    internal_ids[k] = orig_id;
483                    self.nodes[orig_id as usize].parent = NodeIndex::new(my_internal_id, k as u8);
484                }
485            }
486
487            if has_internal {
488                if has_leaf {
489                    internal_ids[new_internal_lane_containing_leaf] = my_leaf_id;
490                    internal_aabbs[new_internal_lane_containing_leaf] = my_leaf_aabb;
491                    // need to merge the leaf aabb into the internal aabb which we did not do previously
492                    my_internal_aabb.merge(&my_leaf_aabb);
493                }
494
495                let internal_node = QbvhNode {
496                    simd_aabb: SimdAabb::from(internal_aabbs),
497                    children: internal_ids,
498                    parent,
499                    flags: QbvhNodeFlags::default(),
500                };
501                // NOTE: we don’t dilate because the AABBs for the rebalancing were
502                //       already dilated during the refit.
503                self.nodes[my_internal_id as usize] = internal_node;
504            }
505
506            if has_leaf {
507                let leaf_node = QbvhNode {
508                    simd_aabb: SimdAabb::from(leaf_aabbs),
509                    children: proxy_ids,
510                    parent: if has_internal {
511                        NodeIndex::new(my_internal_id, new_internal_lane_containing_leaf as u8)
512                    } else {
513                        parent
514                    },
515                    flags: QbvhNodeFlags::LEAF,
516                };
517                // NOTE: we don’t dilate because the AABBs for the rebalancing were
518                //       already dilated during the refit.
519                self.nodes[my_leaf_id as usize] = leaf_node;
520            }
521
522            if has_internal {
523                return (my_internal_id, my_internal_aabb);
524            } else {
525                return (my_leaf_id, my_leaf_aabb);
526            }
527        }
528
529        // Compute the center and variance along each dimension.
530        // In 3D we compute the variance to not-subdivide the dimension with lowest variance.
531        // Therefore variance computation is not needed in 2D because we only have 2 dimension
532        // to split in the first place.
533        let mut center = Point::origin();
534        #[cfg(feature = "dim3")]
535        let mut variance = Vector::zeros();
536
537        let center_denom = 1.0 / (indices.len() as Real);
538
539        for i in &*indices {
540            let coords = workspace.aabbs[*i].center().coords;
541            center += coords * center_denom;
542        }
543
544        #[cfg(feature = "dim3")]
545        {
546            let variance_denom = 1.0 / ((indices.len() - 1) as Real);
547            for i in &*indices {
548                let dir_to_center = workspace.aabbs[*i].center() - center;
549                variance += dir_to_center.component_mul(&dir_to_center) * variance_denom;
550            }
551        }
552
553        // Find the axis with minimum variance. This is the axis along
554        // which we are **not** subdividing our set.
555        #[allow(unused_mut)] // Does not need to be mutable in 2D.
556        let mut subdiv_dims = [0, 1];
557        #[cfg(feature = "dim3")]
558        {
559            let min = variance.imin();
560            subdiv_dims[0] = (min + 1) % 3;
561            subdiv_dims[1] = (min + 2) % 3;
562        }
563
564        let node = QbvhNode {
565            simd_aabb: SimdAabb::new_invalid(),
566            children: [0; 4], // Will be set after the recursive call
567            parent,
568            flags: QbvhNodeFlags::default(),
569        };
570
571        let nid = if let Some(nid) = self.free_list.pop() {
572            self.nodes[nid as usize] = node;
573            nid
574        } else {
575            let nid = self.nodes.len();
576            self.nodes.push(node);
577            nid as u32
578        };
579
580        // Split the set along the two subdiv_dims dimensions.
581        let splitter = CenterDataSplitter::default();
582        let splits =
583            splitter.split_dataset_wo_workspace(subdiv_dims, center, indices, &workspace.aabbs);
584        let n = [
585            NodeIndex::new(nid, 0),
586            NodeIndex::new(nid, 1),
587            NodeIndex::new(nid, 2),
588            NodeIndex::new(nid, 3),
589        ];
590
591        let children = [
592            self.do_recurse_rebalance(splits[0], workspace, n[0], margin),
593            self.do_recurse_rebalance(splits[1], workspace, n[1], margin),
594            self.do_recurse_rebalance(splits[2], workspace, n[2], margin),
595            self.do_recurse_rebalance(splits[3], workspace, n[3], margin),
596        ];
597
598        // Now we know the indices of the child nodes.
599        self.nodes[nid as usize].children =
600            [children[0].0, children[1].0, children[2].0, children[3].0];
601        self.nodes[nid as usize].simd_aabb =
602            SimdAabb::from([children[0].1, children[1].1, children[2].1, children[3].1]);
603        self.nodes[nid as usize]
604            .simd_aabb
605            .loosen(SimdReal::splat(margin));
606
607        let my_aabb = self.nodes[nid as usize].simd_aabb.to_merged_aabb();
608        (nid, my_aabb)
609    }
610}