Skip to main content

box3d_rust/dynamic_tree/
mod.rs

1// Port of box3d-cpp-reference/src/dynamic_tree.c and the tree group of
2// include/box3d/collision.h — a dynamic AABB tree broad-phase inspired by
3// Nathanael Presson's btDbvt.
4//
5// Split to satisfy the 800-line file limit:
6// - insert.rs   — SAH sibling selection, rotations, insert/remove, proxy ops
7// - query.rs    — AABB query, closest query, ray cast, box cast
8// - rebuild.rs  — median-split partial/full rebuild
9// - validate.rs — structure/metrics validation for tests
10//
11// The C `#else` SAH build heuristic (B3_TREE_HEURISTIC == 1) is compiled out
12// upstream and is not ported; only the median-split heuristic is live.
13//
14// Save/Load are debug-only file I/O and are not needed by compound (which
15// embeds nodes by memcpy); they are deferred.
16//
17// The C node uses two unions: {children | userData} and {parent | next}. The
18// Rust node stores all four fields separately; the code writes them at exactly
19// the points the C writes the corresponding union view, so observable behavior
20// matches. (The C default node's children view {-1,-1} reads as u64::MAX
21// through the userData view, which is reproduced explicitly.)
22//
23// SPDX-FileCopyrightText: 2025 Erin Catto
24// SPDX-License-Identifier: MIT
25
26mod insert;
27mod query;
28mod rebuild;
29mod validate;
30
31use crate::core::NULL_INDEX;
32use crate::math_functions::{Aabb, Vec3, VEC3_ZERO};
33
34/// types.h: B3_DEFAULT_CATEGORY_BITS
35pub const DEFAULT_CATEGORY_BITS: u64 = u64::MAX;
36/// types.h: B3_DEFAULT_MASK_BITS
37pub const DEFAULT_MASK_BITS: u64 = u64::MAX;
38
39/// types.h: B3_DYNAMIC_TREE_VERSION
40pub const DYNAMIC_TREE_VERSION: u64 = 0x93EDAF889FD30B4A;
41
42pub(crate) const TREE_STACK_SIZE: usize = 1024;
43
44// Tree node flags (enum b3TreeNodeFlags)
45pub(crate) const ALLOCATED_NODE: u16 = 0x0001;
46pub(crate) const ENLARGED_NODE: u16 = 0x0002;
47pub(crate) const LEAF_NODE: u16 = 0x0004;
48
49/// A node in the dynamic tree. For internal usage. (b3TreeNode)
50#[derive(Debug, Clone, Copy)]
51pub struct TreeNode {
52    /// The node bounding box
53    pub aabb: Aabb,
54    /// Category bits for collision filtering
55    pub category_bits: u64,
56    /// Child node index 1 (internal nodes)
57    pub child1: i32,
58    /// Child node index 2 (internal nodes)
59    pub child2: i32,
60    /// User data (leaf nodes)
61    pub user_data: u64,
62    /// The node parent index (allocated nodes)
63    pub parent: i32,
64    /// The node freelist next index (free nodes)
65    pub next: i32,
66    pub height: u16,
67    pub flags: u16,
68}
69
70impl TreeNode {
71    /// static b3_defaultTreeNode
72    pub(crate) fn default_node() -> TreeNode {
73        TreeNode {
74            aabb: Aabb {
75                lower_bound: VEC3_ZERO,
76                upper_bound: VEC3_ZERO,
77            },
78            category_bits: DEFAULT_CATEGORY_BITS,
79            child1: NULL_INDEX,
80            child2: NULL_INDEX,
81            // The C children/userData union: {-1, -1} reads as u64::MAX.
82            user_data: u64::MAX,
83            parent: NULL_INDEX,
84            next: NULL_INDEX,
85            height: 0,
86            flags: ALLOCATED_NODE,
87        }
88    }
89
90    /// Zeroed storage matching the C memset of the node pool.
91    fn zeroed() -> TreeNode {
92        TreeNode {
93            aabb: Aabb {
94                lower_bound: VEC3_ZERO,
95                upper_bound: VEC3_ZERO,
96            },
97            category_bits: 0,
98            child1: 0,
99            child2: 0,
100            user_data: 0,
101            parent: 0,
102            next: 0,
103            height: 0,
104            flags: 0,
105        }
106    }
107
108    /// (static b3IsLeaf)
109    pub(crate) fn is_leaf(&self) -> bool {
110        self.flags & LEAF_NODE != 0
111    }
112
113    /// (static b3IsAllocated)
114    pub(crate) fn is_allocated(&self) -> bool {
115        self.flags & ALLOCATED_NODE != 0
116    }
117}
118
119/// These are performance results returned by dynamic tree queries. (b3TreeStats)
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
121pub struct TreeStats {
122    /// Number of internal nodes visited during the query
123    pub node_visits: i32,
124    /// Number of leaf nodes visited during the query
125    pub leaf_visits: i32,
126}
127
128/// Input for casting an AABB through a dynamic tree. (b3BoxCastInput)
129#[derive(Debug, Clone, Copy, PartialEq, Default)]
130pub struct BoxCastInput {
131    /// The AABB to cast, in the tree's frame.
132    pub box_: Aabb,
133    /// The sweep translation.
134    pub translation: Vec3,
135    /// The maximum fraction of the translation to consider, typically 1.
136    pub max_fraction: f32,
137}
138
139/// The dynamic tree structure. (b3DynamicTree)
140///
141/// A dynamic AABB tree broad-phase. Leaf nodes are proxies with an AABB, used
142/// to hold a user collision object. Nodes are pooled and relocatable, so node
143/// indices are used rather than pointers.
144#[derive(Debug, Clone)]
145pub struct DynamicTree {
146    /// Dynamic tree version for serialization compatibility.
147    pub(crate) version: u64,
148    /// The tree nodes. `nodes.len()` is the node capacity.
149    pub(crate) nodes: Vec<TreeNode>,
150    /// The root index
151    pub(crate) root: i32,
152    /// The number of allocated nodes
153    pub(crate) node_count: i32,
154    /// Node free list
155    pub(crate) free_list: i32,
156    /// Number of proxies created
157    pub(crate) proxy_count: i32,
158    /// Leaf indices for rebuild
159    pub(crate) leaf_indices: Vec<i32>,
160    /// Leaf bounding box centers for rebuild
161    pub(crate) leaf_centers: Vec<Vec3>,
162    /// Allocated space for rebuilding
163    pub(crate) rebuild_capacity: i32,
164}
165
166impl Default for DynamicTree {
167    fn default() -> Self {
168        Self::new(16)
169    }
170}
171
172impl DynamicTree {
173    /// Constructing the tree initializes the node pool. (b3DynamicTree_Create)
174    pub fn new(proxy_capacity: i32) -> DynamicTree {
175        let capacity = crate::math_functions::max_int(proxy_capacity, 16);
176
177        // maximum node count for a full binary tree is 2 * leafCount - 1
178        let node_capacity = (2 * capacity - 1) as usize;
179        let mut nodes = vec![TreeNode::zeroed(); node_capacity];
180
181        // Build a linked list for the free list.
182        for (i, node) in nodes.iter_mut().enumerate().take(node_capacity - 1) {
183            node.next = i as i32 + 1;
184        }
185        nodes[node_capacity - 1].next = NULL_INDEX;
186
187        DynamicTree {
188            version: DYNAMIC_TREE_VERSION,
189            nodes,
190            root: NULL_INDEX,
191            node_count: 0,
192            free_list: 0,
193            proxy_count: 0,
194            leaf_indices: Vec::new(),
195            leaf_centers: Vec::new(),
196            rebuild_capacity: 0,
197        }
198    }
199
200    /// Destroy the tree, freeing the node pool. (b3DynamicTree_Destroy)
201    ///
202    /// Rust would drop the storage automatically; this mirrors the C function
203    /// (which leaves a zeroed struct) so ported call sites and tests read the
204    /// same.
205    pub fn destroy(&mut self) {
206        *self = DynamicTree {
207            version: 0,
208            nodes: Vec::new(),
209            root: 0,
210            node_count: 0,
211            free_list: 0,
212            proxy_count: 0,
213            leaf_indices: Vec::new(),
214            leaf_centers: Vec::new(),
215            rebuild_capacity: 0,
216        };
217    }
218
219    /// Dynamic tree version for serialization compatibility.
220    /// (b3DynamicTree::version / B3_DYNAMIC_TREE_VERSION)
221    pub fn version(&self) -> u64 {
222        self.version
223    }
224
225    pub(crate) fn node_capacity(&self) -> i32 {
226        self.nodes.len() as i32
227    }
228
229    /// The number of allocated nodes.
230    pub fn node_count(&self) -> i32 {
231        self.node_count
232    }
233
234    /// Allocate a node from the pool. Grow the pool if necessary.
235    /// (static b3AllocateNode)
236    pub(crate) fn allocate_node(&mut self) -> i32 {
237        // Expand the node pool as needed.
238        if self.free_list == NULL_INDEX {
239            debug_assert!(self.node_count == self.node_capacity());
240
241            // The free list is empty. Rebuild a bigger pool.
242            let old_capacity = self.nodes.len();
243            let new_capacity = old_capacity + (old_capacity >> 1);
244            self.nodes.resize(new_capacity, TreeNode::zeroed());
245
246            // Build a linked list for the free list. The parent pointer
247            // becomes the "next" pointer.
248            for i in self.node_count as usize..new_capacity - 1 {
249                self.nodes[i].next = i as i32 + 1;
250            }
251            self.nodes[new_capacity - 1].next = NULL_INDEX;
252            self.free_list = self.node_count;
253        }
254
255        // Peel a node off the free list.
256        let node_index = self.free_list;
257        self.free_list = self.nodes[node_index as usize].next;
258        self.nodes[node_index as usize] = TreeNode::default_node();
259        self.node_count += 1;
260        node_index
261    }
262
263    /// Return a node to the pool. (static b3FreeNode)
264    pub(crate) fn free_node(&mut self, node_id: i32) {
265        debug_assert!(0 <= node_id && node_id < self.node_capacity());
266        debug_assert!(0 < self.node_count);
267        self.nodes[node_id as usize].next = self.free_list;
268        self.nodes[node_id as usize].flags = 0;
269        self.free_list = node_id;
270        self.node_count -= 1;
271    }
272
273    /// Get the number of proxies created. (b3DynamicTree_GetProxyCount)
274    pub fn proxy_count(&self) -> i32 {
275        self.proxy_count
276    }
277
278    /// Get the category bits on a proxy. (b3DynamicTree_GetCategoryBits)
279    pub fn category_bits(&self, proxy_id: i32) -> u64 {
280        debug_assert!(0 <= proxy_id && proxy_id < self.node_capacity());
281        self.nodes[proxy_id as usize].category_bits
282    }
283
284    /// Get the height of the binary tree. (b3DynamicTree_GetHeight)
285    pub fn height(&self) -> i32 {
286        if self.root == NULL_INDEX {
287            return 0;
288        }
289
290        self.nodes[self.root as usize].height as i32
291    }
292
293    /// Get the ratio of the sum of the node areas to the root area.
294    /// (b3DynamicTree_GetAreaRatio)
295    pub fn area_ratio(&self) -> f32 {
296        use crate::aabb::perimeter;
297
298        if self.root == NULL_INDEX {
299            return 0.0;
300        }
301
302        let root = &self.nodes[self.root as usize];
303        let root_area = perimeter(root.aabb);
304
305        let mut total_area = 0.0;
306        for (i, node) in self.nodes.iter().enumerate() {
307            if !node.is_allocated() || node.is_leaf() || i as i32 == self.root {
308                continue;
309            }
310
311            total_area += perimeter(node.aabb);
312        }
313
314        total_area / root_area
315    }
316
317    /// Get the bounding box that contains the entire tree.
318    /// (b3DynamicTree_GetRootBounds)
319    pub fn root_bounds(&self) -> Aabb {
320        if self.root != NULL_INDEX {
321            return self.nodes[self.root as usize].aabb;
322        }
323
324        Aabb {
325            lower_bound: VEC3_ZERO,
326            upper_bound: VEC3_ZERO,
327        }
328    }
329
330    /// Get the number of bytes used by this tree. (b3DynamicTree_GetByteCount)
331    ///
332    /// Matches the C formula, which always counts leafBoxes/binIndices slots
333    /// even when the median-split heuristic leaves those pointers null.
334    pub fn byte_count(&self) -> i32 {
335        let size = core::mem::size_of::<DynamicTree>()
336            + core::mem::size_of::<TreeNode>() * self.nodes.len()
337            + self.rebuild_capacity as usize
338                * (core::mem::size_of::<i32>()
339                    + core::mem::size_of::<Aabb>()
340                    + core::mem::size_of::<Vec3>()
341                    + core::mem::size_of::<i32>());
342
343        size as i32
344    }
345
346    /// Get proxy user data. (b3DynamicTree_GetUserData)
347    pub fn user_data(&self, proxy_id: i32) -> u64 {
348        debug_assert!(0 <= proxy_id && proxy_id < self.node_capacity());
349        self.nodes[proxy_id as usize].user_data
350    }
351
352    /// Get the AABB of a proxy. (b3DynamicTree_GetAABB)
353    pub fn aabb(&self, proxy_id: i32) -> Aabb {
354        debug_assert!(0 <= proxy_id && proxy_id < self.node_capacity());
355        self.nodes[proxy_id as usize].aabb
356    }
357
358    /// The root node index, or [`crate::core::NULL_INDEX`] for an empty tree.
359    /// (b3DynamicTree::root) Exposed read-only for visualization tooling that
360    /// walks the tree structure (the Tree Benchmark sample's depth computation).
361    pub fn root_index(&self) -> i32 {
362        self.root
363    }
364
365    /// A read-only snapshot of every node slot in the pool (allocated and free),
366    /// indexed identically to the C `m_tree.nodes[i]` array so a caller can run the
367    /// sample's breadth-first depth walk and per-level AABB draw. This mirrors the
368    /// C sample reaching directly into `b3DynamicTree::nodes`; the internal fields
369    /// stay private, so this is the only sanctioned window onto them.
370    pub fn node_views(&self) -> Vec<TreeNodeView> {
371        self.nodes
372            .iter()
373            .map(|n| TreeNodeView {
374                aabb: n.aabb,
375                parent: n.parent,
376                child1: n.child1,
377                child2: n.child2,
378                user_data: n.user_data,
379                is_leaf: n.is_leaf(),
380                is_allocated: n.is_allocated(),
381            })
382            .collect()
383    }
384}
385
386/// Read-only view of a single dynamic-tree node, for visualization tooling that
387/// needs the structural fields the C sample reads off `b3TreeNode` directly
388/// (parent/children for a BFS depth walk, `aabb` + flags for per-level drawing).
389#[derive(Debug, Clone, Copy)]
390pub struct TreeNodeView {
391    /// The node bounding box. (b3TreeNode::aabb)
392    pub aabb: Aabb,
393    /// The parent index, or [`crate::core::NULL_INDEX`] for the root. (b3TreeNode::parent)
394    pub parent: i32,
395    /// First child index for internal nodes. (b3TreeNode::children.child1)
396    pub child1: i32,
397    /// Second child index for internal nodes. (b3TreeNode::children.child2)
398    pub child2: i32,
399    /// Leaf user data. (b3TreeNode::userData)
400    pub user_data: u64,
401    /// Whether this node is a leaf (`b3_leafNode`).
402    pub is_leaf: bool,
403    /// Whether this node slot is allocated (`b3_allocatedNode`).
404    pub is_allocated: bool,
405}
406
407/// Category-bit filter matching the C ternary in Query/RayCast/BoxCast.
408#[inline]
409pub(crate) fn category_bits_match(
410    category_bits: u64,
411    mask_bits: u64,
412    require_all_bits: bool,
413) -> bool {
414    if require_all_bits {
415        (category_bits & mask_bits) == mask_bits
416    } else {
417        (category_bits & mask_bits) != 0
418    }
419}