Skip to main content

box3d_rust/
broad_phase.rs

1// Port of box3d-cpp-reference/src/broad_phase.h and broad_phase.c: storage,
2// proxy operations, move buffering, overlap testing, validation, and serial
3// update_broad_phase_pairs.
4//
5// SPDX-FileCopyrightText: 2025 Erin Catto
6// SPDX-License-Identifier: MIT
7
8use crate::bitset::BitSet;
9use crate::body::get_body_transform;
10use crate::core::NULL_INDEX;
11use crate::dynamic_tree::{DynamicTree, DEFAULT_MASK_BITS};
12use crate::geometry::ShapeType;
13use crate::id::ShapeId;
14use crate::math_functions::{
15    aabb_overlaps, aabb_transform, invert_transform, max_int, to_relative_transform, Aabb, POS_ZERO,
16};
17use crate::shape::{shape_flags, ShapeGeometry};
18use crate::table::{shape_pair_key, HashSet};
19use crate::types::{BodyType, Capacity, BODY_TYPE_COUNT};
20use crate::world::World;
21
22/// Store the proxy type in the lower 2 bits of the proxy key. (B3_PROXY_TYPE)
23pub fn proxy_type(key: i32) -> BodyType {
24    match key & 3 {
25        0 => BodyType::Static,
26        1 => BodyType::Kinematic,
27        _ => BodyType::Dynamic,
28    }
29}
30
31/// (B3_PROXY_ID)
32pub fn proxy_id(key: i32) -> i32 {
33    key >> 2
34}
35
36/// (B3_PROXY_KEY)
37pub fn proxy_key(id: i32, type_: BodyType) -> i32 {
38    (id << 2) | (type_ as i32)
39}
40
41/// The broad-phase is used for computing pairs and performing volume queries
42/// and ray casts. It does not persist pairs; it reports potentially new pairs.
43/// (b3BroadPhase)
44#[derive(Debug)]
45pub struct BroadPhase {
46    pub trees: [DynamicTree; BODY_TYPE_COUNT],
47
48    /// Per body-type bit sets indexed by proxyId, marking proxies moved this
49    /// step. Paired with move_array which preserves deterministic insertion
50    /// order for pair queries.
51    pub moved_proxies: [BitSet; BODY_TYPE_COUNT],
52    pub move_array: Vec<i32>,
53
54    /// Tracks shape pairs that have a Contact.
55    pub pair_set: HashSet,
56}
57
58impl BroadPhase {
59    /// (b3CreateBroadPhase)
60    pub fn new(capacity: &Capacity) -> BroadPhase {
61        debug_assert!(BODY_TYPE_COUNT == 3);
62
63        let static_capacity = max_int(16, capacity.static_shape_count);
64        let kinematic_capacity = 16;
65        let dynamic_capacity = max_int(16, capacity.dynamic_shape_count);
66
67        let mut move_array = Vec::new();
68        move_array.reserve(capacity.dynamic_shape_count.max(0) as usize);
69
70        BroadPhase {
71            trees: [
72                DynamicTree::new(static_capacity),
73                DynamicTree::new(kinematic_capacity),
74                DynamicTree::new(dynamic_capacity),
75            ],
76            moved_proxies: [
77                BitSet::new(max_int(16, capacity.static_shape_count) as u32),
78                BitSet::new(16),
79                BitSet::new(max_int(16, capacity.dynamic_shape_count) as u32),
80            ],
81            move_array,
82            // C: b3CreateSet(2 * capacity->contactCount)
83            pair_set: HashSet::new(2 * capacity.contact_count),
84        }
85    }
86
87    /// (b3DestroyBroadPhase)
88    pub fn destroy(&mut self) {
89        *self = BroadPhase {
90            trees: [
91                DynamicTree::new(0),
92                DynamicTree::new(0),
93                DynamicTree::new(0),
94            ],
95            moved_proxies: [BitSet::new(0), BitSet::new(0), BitSet::new(0)],
96            move_array: Vec::new(),
97            pair_set: HashSet::new(16),
98        };
99    }
100
101    /// This triggers new contact pairs to be created. Must be called in
102    /// deterministic order. (static inline b3BufferMove)
103    pub fn buffer_move(&mut self, query_proxy: i32) {
104        let proxy_type_ = proxy_type(query_proxy);
105        let proxy_id_ = proxy_id(query_proxy);
106        let set = &mut self.moved_proxies[proxy_type_ as usize];
107        if !set.get_bit(proxy_id_ as u32) {
108            set.set_bit_grow(proxy_id_ as u32);
109            self.move_array.push(query_proxy);
110        }
111    }
112
113    /// (b3BroadPhase_CreateProxy)
114    pub fn create_proxy(
115        &mut self,
116        proxy_type_: BodyType,
117        aabb: Aabb,
118        category_bits: u64,
119        shape_index: i32,
120        force_pair_creation: bool,
121    ) -> i32 {
122        debug_assert!((proxy_type_ as usize) < BODY_TYPE_COUNT);
123        let proxy_id_ =
124            self.trees[proxy_type_ as usize].create_proxy(aabb, category_bits, shape_index as u64);
125        let proxy_key_ = proxy_key(proxy_id_, proxy_type_);
126        if proxy_type_ != BodyType::Static || force_pair_creation {
127            self.buffer_move(proxy_key_);
128        }
129        proxy_key_
130    }
131
132    /// (static b3UnBufferMove)
133    fn unbuffer_move(&mut self, proxy_key_: i32) {
134        let proxy_type_ = proxy_type(proxy_key_);
135        let proxy_id_ = proxy_id(proxy_key_);
136        let set = &mut self.moved_proxies[proxy_type_ as usize];
137
138        if set.get_bit(proxy_id_ as u32) {
139            set.clear_bit(proxy_id_ as u32);
140
141            // Purge from move buffer. Linear search. (b3Array_RemoveSwap)
142            if let Some(index) = self.move_array.iter().position(|&k| k == proxy_key_) {
143                self.move_array.swap_remove(index);
144            }
145        }
146    }
147
148    /// (b3BroadPhase_DestroyProxy)
149    pub fn destroy_proxy(&mut self, proxy_key_: i32) {
150        self.unbuffer_move(proxy_key_);
151
152        let proxy_type_ = proxy_type(proxy_key_);
153        let proxy_id_ = proxy_id(proxy_key_);
154
155        debug_assert!((proxy_type_ as usize) <= BODY_TYPE_COUNT);
156        self.trees[proxy_type_ as usize].destroy_proxy(proxy_id_);
157    }
158
159    /// (b3BroadPhase_MoveProxy)
160    pub fn move_proxy(&mut self, proxy_key_: i32, aabb: Aabb) {
161        let proxy_type_ = proxy_type(proxy_key_);
162        let proxy_id_ = proxy_id(proxy_key_);
163
164        self.trees[proxy_type_ as usize].move_proxy(proxy_id_, aabb);
165        self.buffer_move(proxy_key_);
166    }
167
168    /// (b3BroadPhase_EnlargeProxy)
169    pub fn enlarge_proxy(&mut self, proxy_key_: i32, aabb: Aabb) {
170        debug_assert!(proxy_key_ != crate::core::NULL_INDEX);
171        let proxy_type_ = proxy_type(proxy_key_);
172        let proxy_id_ = proxy_id(proxy_key_);
173
174        debug_assert!(proxy_type_ != BodyType::Static);
175
176        self.trees[proxy_type_ as usize].enlarge_proxy(proxy_id_, aabb);
177        self.buffer_move(proxy_key_);
178    }
179
180    /// (b3BroadPhase_GetShapeIndex)
181    pub fn shape_index(&self, proxy_key_: i32) -> i32 {
182        let proxy_type_ = proxy_type(proxy_key_);
183        let proxy_id_ = proxy_id(proxy_key_);
184
185        self.trees[proxy_type_ as usize].user_data(proxy_id_) as i32
186    }
187
188    /// (b3BroadPhase_TestOverlap)
189    pub fn test_overlap(&self, proxy_key_a: i32, proxy_key_b: i32) -> bool {
190        let type_a = proxy_type(proxy_key_a);
191        let id_a = proxy_id(proxy_key_a);
192        let type_b = proxy_type(proxy_key_b);
193        let id_b = proxy_id(proxy_key_b);
194
195        let aabb_a = self.trees[type_a as usize].aabb(id_a);
196        let aabb_b = self.trees[type_b as usize].aabb(id_b);
197        aabb_overlaps(aabb_a, aabb_b)
198    }
199
200    /// (b3ValidateBroadPhase)
201    pub fn validate(&self) {
202        self.trees[BodyType::Dynamic as usize].validate();
203        self.trees[BodyType::Kinematic as usize].validate();
204    }
205
206    /// (b3ValidateNoEnlarged — C compiles the body under B3_ENABLE_VALIDATION;
207    /// here the check runs in debug builds only)
208    pub fn validate_no_enlarged(&self) {
209        if cfg!(debug_assertions) {
210            for tree in &self.trees {
211                tree.validate_no_enlarged();
212            }
213        }
214    }
215
216    /// Invariant: bit set in movedProxies[type] iff proxyKey is in moveArray.
217    pub fn validate_moved_proxies(&self) {
218        if cfg!(debug_assertions) {
219            for &proxy_key_ in &self.move_array {
220                let proxy_type_ = proxy_type(proxy_key_);
221                let proxy_id_ = proxy_id(proxy_key_);
222                debug_assert!(self.moved_proxies[proxy_type_ as usize].get_bit(proxy_id_ as u32));
223            }
224
225            let mut total_set_bits = 0;
226            for i in 0..BODY_TYPE_COUNT {
227                total_set_bits += self.moved_proxies[i].count_set_bits();
228            }
229            debug_assert!(total_set_bits == self.move_array.len() as i32);
230        }
231    }
232}
233
234/// Consider one shape pair candidate for the move-pair list.
235/// (body of b3PairQueryCallback after compound resolution)
236fn consider_move_pair(
237    world: &World,
238    tree_type: BodyType,
239    query_proxy_key: i32,
240    query_shape_index: i32,
241    shape_index: i32,
242    proxy_id_: i32,
243    child_index: i32,
244    pair_list: &mut Vec<(i32, i32, i32)>,
245) {
246    let proxy_key_ = proxy_key(proxy_id_, tree_type);
247    debug_assert!(proxy_key_ != query_proxy_key);
248
249    let query_proxy_type = proxy_type(query_proxy_key);
250    let bp = &world.broad_phase;
251
252    // De-duplication when both proxies are moving.
253    if query_proxy_type == BodyType::Dynamic {
254        if tree_type == BodyType::Dynamic && proxy_key_ < query_proxy_key {
255            if bp.moved_proxies[tree_type as usize].get_bit(proxy_id_ as u32) {
256                return;
257            }
258        }
259    } else {
260        debug_assert!(tree_type == BodyType::Dynamic);
261        if bp.moved_proxies[tree_type as usize].get_bit(proxy_id_ as u32) {
262            return;
263        }
264    }
265
266    let pair_key = shape_pair_key(shape_index, query_shape_index, child_index);
267    if bp.pair_set.contains_key(pair_key) {
268        return;
269    }
270
271    let shape_id_a = shape_index;
272    let shape_id_b = query_shape_index;
273    let shape_a = &world.shapes[shape_id_a as usize];
274    let shape_b = &world.shapes[shape_id_b as usize];
275    let body_id_a = shape_a.body_id;
276    let body_id_b = shape_b.body_id;
277
278    if body_id_a == body_id_b {
279        return;
280    }
281
282    if shape_a.sensor_index != NULL_INDEX || shape_b.sensor_index != NULL_INDEX {
283        return;
284    }
285
286    if !crate::shape::should_shapes_collide(shape_a.filter, shape_b.filter) {
287        return;
288    }
289
290    if !crate::body::should_bodies_collide(world, body_id_a, body_id_b) {
291        return;
292    }
293
294    if (shape_a.flags & shape_flags::ENABLE_CUSTOM_FILTERING) != 0
295        || (shape_b.flags & shape_flags::ENABLE_CUSTOM_FILTERING) != 0
296    {
297        if let Some(custom_filter_fcn) = world.custom_filter_fcn {
298            let id_a = ShapeId {
299                index1: shape_id_a + 1,
300                world0: world.world_id,
301                generation: shape_a.generation,
302            };
303            let id_b = ShapeId {
304                index1: shape_id_b + 1,
305                world0: world.world_id,
306                generation: shape_b.generation,
307            };
308            if !custom_filter_fcn(world, id_a, id_b, world.custom_filter_context) {
309                return;
310            }
311        }
312    }
313
314    if !crate::contact::can_collide(shape_a.shape_type(), shape_b.shape_type()) {
315        return;
316    }
317
318    pair_list.push((shape_id_a, shape_id_b, child_index));
319}
320
321/// Query one tree for new pairs against a moved proxy.
322/// (b3PairQueryCallback — serial, with compound child recursion)
323fn query_tree_for_pairs(
324    world: &World,
325    tree_type: BodyType,
326    query_proxy_key: i32,
327    query_shape_index: i32,
328    fat_aabb: Aabb,
329    pair_list: &mut Vec<(i32, i32, i32)>,
330) {
331    world.broad_phase.trees[tree_type as usize].query(
332        fat_aabb,
333        DEFAULT_MASK_BITS,
334        false,
335        |proxy_id_, user_data| {
336            let shape_index = user_data as i32;
337            if shape_index == query_shape_index {
338                return true;
339            }
340
341            if world.shapes[shape_index as usize].shape_type() == ShapeType::Compound {
342                // Query bounds are float world space; demote the body transform to the
343                // matching float frame, then recurse into the compound BVH.
344                let body_id = world.shapes[shape_index as usize].body_id;
345                let compound_transform =
346                    to_relative_transform(get_body_transform(world, body_id), POS_ZERO);
347                let local_aabb = aabb_transform(invert_transform(compound_transform), fat_aabb);
348
349                let mut child_indices = Vec::new();
350                if let ShapeGeometry::Compound(compound) =
351                    &world.shapes[shape_index as usize].geometry
352                {
353                    compound.tree.query(
354                        local_aabb,
355                        DEFAULT_MASK_BITS,
356                        false,
357                        |_child_proxy, child_user_data| {
358                            child_indices.push(child_user_data as i32);
359                            true
360                        },
361                    );
362                }
363
364                for child_index in child_indices {
365                    consider_move_pair(
366                        world,
367                        tree_type,
368                        query_proxy_key,
369                        query_shape_index,
370                        shape_index,
371                        proxy_id_,
372                        child_index,
373                        pair_list,
374                    );
375                }
376                return true;
377            }
378
379            consider_move_pair(
380                world,
381                tree_type,
382                query_proxy_key,
383                query_shape_index,
384                shape_index,
385                proxy_id_,
386                0,
387                pair_list,
388            );
389            true
390        },
391    );
392}
393
394/// Find new proxy pairs and create contacts in deterministic move-array order.
395/// (b3UpdateBroadPhasePairs — serial)
396pub fn update_broad_phase_pairs(world: &mut World) {
397    world.broad_phase.validate_moved_proxies();
398
399    let move_count = world.broad_phase.move_array.len();
400    if move_count == 0 {
401        return;
402    }
403
404    let mut move_results: Vec<Vec<(i32, i32, i32)>> = Vec::with_capacity(move_count);
405    for i in 0..move_count {
406        let mut pair_list: Vec<(i32, i32, i32)> = Vec::new();
407        let proxy_key_ = world.broad_phase.move_array[i];
408        if proxy_key_ == NULL_INDEX {
409            move_results.push(pair_list);
410            continue;
411        }
412
413        let proxy_type_ = proxy_type(proxy_key_);
414        let proxy_id_ = proxy_id(proxy_key_);
415        let base_tree = &world.broad_phase.trees[proxy_type_ as usize];
416        let fat_aabb = base_tree.aabb(proxy_id_);
417        let query_shape_index = base_tree.user_data(proxy_id_) as i32;
418
419        debug_assert!(world.shapes[query_shape_index as usize].shape_type() != ShapeType::Compound);
420
421        if proxy_type_ == BodyType::Dynamic {
422            query_tree_for_pairs(
423                world,
424                BodyType::Kinematic,
425                proxy_key_,
426                query_shape_index,
427                fat_aabb,
428                &mut pair_list,
429            );
430            query_tree_for_pairs(
431                world,
432                BodyType::Static,
433                proxy_key_,
434                query_shape_index,
435                fat_aabb,
436                &mut pair_list,
437            );
438        }
439
440        query_tree_for_pairs(
441            world,
442            BodyType::Dynamic,
443            proxy_key_,
444            query_shape_index,
445            fat_aabb,
446            &mut pair_list,
447        );
448
449        move_results.push(pair_list);
450    }
451
452    world.broad_phase.trees[BodyType::Dynamic as usize].rebuild(false);
453    world.broad_phase.trees[BodyType::Kinematic as usize].rebuild(false);
454
455    // C prepends to a linked list then walks head-first (= reverse discovery).
456    for pair_list in &move_results {
457        for &(shape_id_a, shape_id_b, child_index) in pair_list.iter().rev() {
458            crate::contact::create_contact(world, shape_id_a, shape_id_b, child_index);
459        }
460    }
461
462    for i in 0..world.broad_phase.move_array.len() {
463        let proxy_key_ = world.broad_phase.move_array[i];
464        let proxy_type_ = proxy_type(proxy_key_);
465        let proxy_id_ = proxy_id(proxy_key_);
466        world.broad_phase.moved_proxies[proxy_type_ as usize].clear_bit(proxy_id_ as u32);
467    }
468    world.broad_phase.move_array.clear();
469
470    world.validate_solver_sets();
471}