Skip to main content

box3d_rust/dynamic_tree/
query.rs

1// AABB query, closest query, ray cast, and box cast from dynamic_tree.c.
2//
3// The C callbacks take a `void* context`; the Rust versions take closures,
4// which capture their context directly.
5//
6// SPDX-FileCopyrightText: 2025 Erin Catto
7// SPDX-License-Identifier: MIT
8
9use super::{category_bits_match, BoxCastInput, DynamicTree, TreeStats, TREE_STACK_SIZE};
10use crate::core::NULL_INDEX;
11use crate::geometry::RayCastInput;
12use crate::math_functions::{
13    aabb_center, aabb_extents, aabb_overlaps, add, clamp, distance_squared, dot, max, min, mul_add,
14    mul_sv, sub, test_bounds_ray_overlap, Aabb, Vec3,
15};
16
17/// Squared distance from a point to a node AABB. (static b3DistanceToNodeSqr)
18fn distance_to_node_sqr(point: Vec3, node_aabb: Aabb) -> f32 {
19    let r = sub(
20        point,
21        clamp(point, node_aabb.lower_bound, node_aabb.upper_bound),
22    );
23    dot(r, r)
24}
25
26#[derive(Clone, Copy)]
27struct QueryClosestItem {
28    node_index: i32,
29    distance_to_node_sqr: f32,
30}
31
32impl DynamicTree {
33    /// Query an AABB for overlapping proxies. The callback is called for each
34    /// proxy that overlaps the supplied AABB and passes the mask-bits filter;
35    /// return false from the callback to stop. (b3DynamicTree_Query)
36    pub fn query(
37        &self,
38        aabb: Aabb,
39        mask_bits: u64,
40        require_all_bits: bool,
41        mut callback: impl FnMut(i32, u64) -> bool,
42    ) -> TreeStats {
43        let mut result = TreeStats::default();
44
45        if self.node_count == 0 {
46            return result;
47        }
48
49        let mut stack = [0i32; TREE_STACK_SIZE];
50        let mut stack_count = 0usize;
51        stack[stack_count] = self.root;
52        stack_count += 1;
53
54        while stack_count > 0 {
55            stack_count -= 1;
56            let node_id = stack[stack_count];
57            if node_id == NULL_INDEX {
58                debug_assert!(false);
59                continue;
60            }
61
62            let node = &self.nodes[node_id as usize];
63            result.node_visits += 1;
64
65            if category_bits_match(node.category_bits, mask_bits, require_all_bits)
66                && aabb_overlaps(node.aabb, aabb)
67            {
68                if node.is_leaf() {
69                    // callback to user code with proxy id
70                    let proceed = callback(node_id, node.user_data);
71                    result.leaf_visits += 1;
72
73                    if !proceed {
74                        return result;
75                    }
76                } else {
77                    debug_assert!(stack_count < TREE_STACK_SIZE - 1);
78                    if stack_count < TREE_STACK_SIZE - 1 {
79                        stack[stack_count] = node.child1;
80                        stack_count += 1;
81                        stack[stack_count] = node.child2;
82                        stack_count += 1;
83                    }
84                }
85            }
86        }
87
88        result
89    }
90
91    /// Query for the closest proxy to a point. The callback receives the current
92    /// minimum squared distance and returns an updated distance for that proxy.
93    /// (b3DynamicTree_QueryClosest)
94    pub fn query_closest(
95        &self,
96        point: Vec3,
97        mask_bits: u64,
98        require_all_bits: bool,
99        mut callback: impl FnMut(f32, i32, u64) -> f32,
100        min_distance_sqr: &mut f32,
101    ) -> TreeStats {
102        let mut result = TreeStats::default();
103
104        if self.node_count == 0 {
105            return result;
106        }
107
108        let mut min_sqr = *min_distance_sqr;
109        let mut stack = [QueryClosestItem {
110            node_index: 0,
111            distance_to_node_sqr: 0.0,
112        }; TREE_STACK_SIZE];
113        let mut stack_count = 0usize;
114
115        let root_distance_sqr = distance_to_node_sqr(point, self.nodes[self.root as usize].aabb);
116        stack[stack_count] = QueryClosestItem {
117            node_index: self.root,
118            distance_to_node_sqr: root_distance_sqr,
119        };
120        stack_count += 1;
121
122        while stack_count > 0 {
123            stack_count -= 1;
124            let item = stack[stack_count];
125            let node = &self.nodes[item.node_index as usize];
126            result.node_visits += 1;
127
128            if category_bits_match(node.category_bits, mask_bits, require_all_bits)
129                && item.distance_to_node_sqr < min_sqr
130            {
131                if node.is_leaf() {
132                    let dd = callback(min_sqr, item.node_index, node.user_data);
133
134                    if dd < min_sqr {
135                        min_sqr = dd;
136                    }
137
138                    result.leaf_visits += 1;
139                } else {
140                    debug_assert!(stack_count < TREE_STACK_SIZE - 1);
141                    if stack_count < TREE_STACK_SIZE - 1 {
142                        let child1 = node.child1;
143                        let child2 = node.child2;
144
145                        let item1 = QueryClosestItem {
146                            node_index: child1,
147                            distance_to_node_sqr: distance_to_node_sqr(
148                                point,
149                                self.nodes[child1 as usize].aabb,
150                            ),
151                        };
152
153                        let item2 = QueryClosestItem {
154                            node_index: child2,
155                            distance_to_node_sqr: distance_to_node_sqr(
156                                point,
157                                self.nodes[child2 as usize].aabb,
158                            ),
159                        };
160
161                        // Ensure we iterate the closest child first as we pop
162                        if item2.distance_to_node_sqr < item1.distance_to_node_sqr {
163                            stack[stack_count] = item1;
164                            stack_count += 1;
165                            stack[stack_count] = item2;
166                            stack_count += 1;
167                        } else {
168                            stack[stack_count] = item2;
169                            stack_count += 1;
170                            stack[stack_count] = item1;
171                            stack_count += 1;
172                        }
173                    }
174                }
175            }
176        }
177
178        *min_distance_sqr = min_sqr;
179
180        result
181    }
182
183    /// Ray cast against the proxies in the tree. The callback performs an
184    /// exact ray cast when the proxy contains a shape, and returns the new
185    /// ray fraction:
186    /// - return 0 to terminate the ray cast
187    /// - return a value less than the input max_fraction to clip the ray
188    /// - return the input max_fraction to continue without clipping
189    ///
190    /// (b3DynamicTree_RayCast)
191    pub fn ray_cast(
192        &self,
193        input: &RayCastInput,
194        mask_bits: u64,
195        require_all_bits: bool,
196        mut callback: impl FnMut(&RayCastInput, i32, u64) -> f32,
197    ) -> TreeStats {
198        let mut result = TreeStats::default();
199
200        if self.node_count == 0 {
201            return result;
202        }
203
204        let p1 = input.origin;
205        let d = input.translation;
206
207        let mut max_fraction = input.max_fraction;
208
209        let mut p2 = mul_add(p1, max_fraction, d);
210
211        // Build a bounding box for the segment.
212        let mut segment_aabb = Aabb {
213            lower_bound: min(p1, p2),
214            upper_bound: max(p1, p2),
215        };
216
217        let mut stack = [0i32; TREE_STACK_SIZE];
218        let mut stack_count = 0usize;
219        stack[stack_count] = self.root;
220        stack_count += 1;
221
222        let mut sub_input = *input;
223
224        while stack_count > 0 {
225            stack_count -= 1;
226            let node_id = stack[stack_count];
227            if node_id == NULL_INDEX {
228                debug_assert!(false);
229                continue;
230            }
231
232            let node = &self.nodes[node_id as usize];
233            result.node_visits += 1;
234
235            let node_aabb = node.aabb;
236
237            if !category_bits_match(node.category_bits, mask_bits, require_all_bits)
238                || !aabb_overlaps(node_aabb, segment_aabb)
239            {
240                continue;
241            }
242
243            if !test_bounds_ray_overlap(node_aabb.lower_bound, node_aabb.upper_bound, p1, d) {
244                continue;
245            }
246
247            if node.is_leaf() {
248                sub_input.max_fraction = max_fraction;
249
250                let value = callback(&sub_input, node_id, node.user_data);
251                result.leaf_visits += 1;
252
253                // The user may return -1 to indicate this shape should be skipped
254
255                if value == 0.0 {
256                    // The client has terminated the ray cast.
257                    return result;
258                }
259
260                if 0.0 < value && value <= max_fraction {
261                    // Update segment bounding box.
262                    max_fraction = value;
263                    p2 = mul_add(p1, max_fraction, d);
264                    segment_aabb.lower_bound = min(p1, p2);
265                    segment_aabb.upper_bound = max(p1, p2);
266                }
267            } else {
268                debug_assert!(stack_count < TREE_STACK_SIZE - 1);
269                if stack_count < TREE_STACK_SIZE - 1 {
270                    let c1 = aabb_center(self.nodes[node.child1 as usize].aabb);
271                    let c2 = aabb_center(self.nodes[node.child2 as usize].aabb);
272                    if distance_squared(c1, p1) < distance_squared(c2, p1) {
273                        stack[stack_count] = node.child2;
274                        stack_count += 1;
275                        stack[stack_count] = node.child1;
276                        stack_count += 1;
277                    } else {
278                        stack[stack_count] = node.child1;
279                        stack_count += 1;
280                        stack[stack_count] = node.child2;
281                        stack_count += 1;
282                    }
283                }
284            }
285        }
286
287        result
288    }
289
290    /// Cast a swept AABB through the tree. The callback returns the new cast
291    /// fraction, with the same semantics as [`DynamicTree::ray_cast`].
292    /// (b3DynamicTree_BoxCast)
293    pub fn box_cast(
294        &self,
295        input: &BoxCastInput,
296        mask_bits: u64,
297        require_all_bits: bool,
298        mut callback: impl FnMut(&BoxCastInput, i32, u64) -> f32,
299    ) -> TreeStats {
300        let mut stats = TreeStats::default();
301
302        if self.node_count == 0 {
303            return stats;
304        }
305
306        // The caller folds the shape radius and the world origin into the box
307        let origin_aabb = input.box_;
308
309        let p1 = aabb_center(origin_aabb);
310        let extension = aabb_extents(origin_aabb);
311
312        let d = input.translation;
313
314        let mut max_fraction = input.max_fraction;
315
316        // Build total box for the cast
317        let mut t = mul_sv(max_fraction, input.translation);
318        let mut total_aabb = Aabb {
319            lower_bound: min(origin_aabb.lower_bound, add(origin_aabb.lower_bound, t)),
320            upper_bound: max(origin_aabb.upper_bound, add(origin_aabb.upper_bound, t)),
321        };
322
323        let mut sub_input = *input;
324
325        let mut stack = [0i32; TREE_STACK_SIZE];
326        let mut stack_count = 0usize;
327        stack[stack_count] = self.root;
328        stack_count += 1;
329
330        while stack_count > 0 {
331            stack_count -= 1;
332            let node_id = stack[stack_count];
333            if node_id == NULL_INDEX {
334                debug_assert!(false);
335                continue;
336            }
337
338            let node = &self.nodes[node_id as usize];
339            stats.node_visits += 1;
340
341            if !category_bits_match(node.category_bits, mask_bits, require_all_bits)
342                || !aabb_overlaps(node.aabb, total_aabb)
343            {
344                continue;
345            }
346
347            // radius extension is added to the node in this case
348            let lower = sub(node.aabb.lower_bound, extension);
349            let upper = add(node.aabb.upper_bound, extension);
350            if !test_bounds_ray_overlap(lower, upper, p1, d) {
351                continue;
352            }
353
354            if node.is_leaf() {
355                sub_input.max_fraction = max_fraction;
356
357                let value = callback(&sub_input, node_id, node.user_data);
358                stats.leaf_visits += 1;
359
360                if value == 0.0 {
361                    // The client has terminated the cast.
362                    return stats;
363                }
364
365                if 0.0 < value && value < max_fraction {
366                    max_fraction = value;
367                    t = mul_sv(max_fraction, input.translation);
368                    total_aabb.lower_bound =
369                        min(origin_aabb.lower_bound, add(origin_aabb.lower_bound, t));
370                    total_aabb.upper_bound =
371                        max(origin_aabb.upper_bound, add(origin_aabb.upper_bound, t));
372                }
373            } else {
374                debug_assert!(stack_count < TREE_STACK_SIZE - 1);
375                if stack_count < TREE_STACK_SIZE - 1 {
376                    let c1 = aabb_center(self.nodes[node.child1 as usize].aabb);
377                    let c2 = aabb_center(self.nodes[node.child2 as usize].aabb);
378                    if distance_squared(c1, p1) < distance_squared(c2, p1) {
379                        stack[stack_count] = node.child2;
380                        stack_count += 1;
381                        stack[stack_count] = node.child1;
382                        stack_count += 1;
383                    } else {
384                        stack[stack_count] = node.child1;
385                        stack_count += 1;
386                        stack[stack_count] = node.child2;
387                        stack_count += 1;
388                    }
389                }
390            }
391        }
392
393        stats
394    }
395}