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}