Skip to main content

box2d_rust/world/
collide.rs

1// The narrow phase from physics_world.c: b2CollideTask/b2Collide (narrow phase
2// + contact state transitions), plus b2ValidateContacts.
3//
4// The serial port gathers contact locations instead of the C's arena array of
5// ContactSim pointers, and processes them in the same order: graph colors
6// 0..GRAPH_COLOR_COUNT (overflow included) then the awake set's non-touching
7// contacts.
8//
9// SPDX-FileCopyrightText: 2023 Erin Catto
10// SPDX-License-Identifier: MIT
11
12use super::World;
13use crate::body::BodySim;
14use crate::constants::{speculative_distance, GRAPH_COLOR_COUNT};
15use crate::contact::{contact_flags, update_contact, Contact, ContactSim, ContactUpdateContext};
16use crate::core::NULL_INDEX;
17use crate::events::{ContactBeginTouchEvent, ContactEndTouchEvent};
18use crate::id::{ContactId, ShapeId};
19use crate::math_functions::{
20    aabb_overlaps, abs_float, distance, dot, inv_mul_rot, inv_mul_world_transforms, invert_rot,
21    max_float, min_float, mul_rot, rotate_vector, sub, sub_pos, Rot,
22};
23use crate::solver::StepContext;
24use crate::solver_set::{SolverSet, AWAKE_SET, FIRST_SLEEPING_SET};
25use crate::types::BodyType;
26
27/// (static inline b2RelativeCos)
28fn relative_cos(a: Rot, b: Rot) -> f32 {
29    a.c * b.c + a.s * b.s
30}
31
32/// Location of a contact sim during the narrow phase: a graph color index or
33/// NULL_INDEX for the awake set's non-touching array, plus the local index.
34/// (The C gathers b2ContactSim pointers; pointers are unstable in Rust.)
35#[derive(Clone, Copy)]
36struct ContactSimLocation {
37    color_index: i32,
38    local_index: i32,
39}
40
41/// Borrow a body sim in place from the split solver sets. The solver sets are
42/// split so the awake set's non-touching contact sims can be borrowed mutably
43/// while its body sims (and those of the static/sleeping sets) are read through
44/// shared references, mirroring the C which holds raw pointers to the contact
45/// sim and both body sims at once (physics_world.c b2CollideTask).
46fn located_body_sim<'a>(
47    head_sets: &'a [SolverSet],
48    awake_body_sims: &'a [BodySim],
49    sleeping_sets: &'a [SolverSet],
50    set_index: i32,
51    local_index: i32,
52) -> &'a BodySim {
53    let local = local_index as usize;
54    if set_index == AWAKE_SET {
55        &awake_body_sims[local]
56    } else if set_index < AWAKE_SET {
57        &head_sets[set_index as usize].body_sims[local]
58    } else {
59        &sleeping_sets[(set_index - FIRST_SLEEPING_SET) as usize].body_sims[local]
60    }
61}
62
63/// Update one contact in the narrow phase. (the b2CollideTask loop body)
64fn collide_contact(
65    world: &mut World,
66    location: ContactSimLocation,
67    update_context: &ContactUpdateContext,
68) {
69    let recycle_distance = world.contact_recycle_distance;
70    let speculative_distance_ = speculative_distance();
71    let recycle_distance_non_touching = min_float(recycle_distance, speculative_distance_);
72
73    // Split the solver sets so the awake set's non-touching contact sims can be
74    // mutated in place while the body sims (which may live in the same set) are
75    // read through shared references. This mirrors the C, which holds raw
76    // pointers to the contact sim and both body sims at once, avoiding the
77    // per-contact ContactSim/BodySim copies of the earlier port.
78    let (head_sets, tail_sets) = world.solver_sets.split_at_mut(AWAKE_SET as usize);
79    let (awake_slice, sleeping_sets) = tail_sets.split_at_mut(1);
80    let SolverSet {
81        contact_sims: awake_contact_sims,
82        body_sims: awake_body_sims,
83        ..
84    } = &mut awake_slice[0];
85
86    // Update the contact sim in place: touching contacts live in the constraint
87    // graph, non-touching contacts in the awake set.
88    let contact_sim: &mut ContactSim = if location.color_index != NULL_INDEX {
89        &mut world.constraint_graph.colors[location.color_index as usize].contact_sims
90            [location.local_index as usize]
91    } else {
92        &mut awake_contact_sims[location.local_index as usize]
93    };
94
95    let contact_id = contact_sim.contact_id;
96
97    // Do proxies still overlap?
98    let overlap = {
99        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
100        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
101        aabb_overlaps(shape_a.fat_aabb, shape_b.fat_aabb)
102    };
103
104    if !overlap {
105        contact_sim.sim_flags |= contact_flags::SIM_DISJOINT;
106        contact_sim.sim_flags &= !contact_flags::SIM_TOUCHING;
107        world.task_contexts[0]
108            .contact_state_bit_set
109            .set_bit(contact_id as u32);
110        return;
111    }
112
113    let was_touching = contact_sim.sim_flags & contact_flags::SIM_TOUCHING != 0;
114
115    // Update contact respecting shape/body order (A,B)
116    let (shape_body_id_a, shape_body_id_b) = {
117        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
118        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
119        (shape_a.body_id, shape_b.body_id)
120    };
121
122    let body_a = &world.bodies[shape_body_id_a as usize];
123    let body_b = &world.bodies[shape_body_id_b as usize];
124    let body_sim_a = located_body_sim(
125        &*head_sets,
126        &*awake_body_sims,
127        &*sleeping_sets,
128        body_a.set_index,
129        body_a.local_index,
130    );
131    let body_sim_b = located_body_sim(
132        &*head_sets,
133        &*awake_body_sims,
134        &*sleeping_sets,
135        body_b.set_index,
136        body_b.local_index,
137    );
138    let transform_a = body_sim_a.transform;
139    let transform_b = body_sim_b.transform;
140
141    // These may not be skipped by relative transform check below
142    contact_sim.body_sim_index_a = if body_a.set_index == AWAKE_SET {
143        body_a.local_index
144    } else {
145        NULL_INDEX
146    };
147    contact_sim.inv_mass_a = body_sim_a.inv_mass;
148    contact_sim.inv_i_a = body_sim_a.inv_inertia;
149
150    contact_sim.body_sim_index_b = if body_b.set_index == AWAKE_SET {
151        body_b.local_index
152    } else {
153        NULL_INDEX
154    };
155    contact_sim.inv_mass_b = body_sim_b.inv_mass;
156    contact_sim.inv_i_b = body_sim_b.inv_inertia;
157
158    let type_a = body_a.type_;
159    let type_b = body_b.type_;
160
161    // Contact recycling optimization. Please cite this code if you use this
162    // optimization. This is inspired by persistent contact manifolds used in
163    // some physics engines, such as PhysX. However, this allows larger
164    // relative motion and has fewer tuning parameters (just one).
165    if recycle_distance > 0.0
166        && contact_sim.sim_flags & contact_flags::SIM_RELATIVE_TRANSFORM_VALID != 0
167        && contact_sim.sim_flags & contact_flags::RECYCLE != 0
168    {
169        let cached_q_a = contact_sim.cached_rotation_a;
170        let cached_q_b = contact_sim.cached_rotation_b;
171        let xfc = contact_sim.cached_relative_pose;
172        let xf = inv_mul_world_transforms(transform_a, transform_b);
173
174        let cos_a = relative_cos(transform_a.q, cached_q_a);
175        let cos_b = relative_cos(transform_b.q, cached_q_b);
176        let min_cos = min_float(cos_a, cos_b);
177
178        let max_extent_a = if type_a == BodyType::Static {
179            0.0
180        } else {
181            body_sim_a.max_extent
182        };
183        let max_extent_b = if type_b == BodyType::Static {
184            0.0
185        } else {
186            body_sim_b.max_extent
187        };
188        let max_extent = max_float(max_extent_a, max_extent_b);
189        let distance_ = distance(xf.p, xfc.p);
190        let qr = inv_mul_rot(xf.q, xfc.q);
191
192        // This metric is used for fast bodies and sleeping. It comes from
193        // conservative advancement. Note that qr.s == sin(theta) ~= theta for
194        // small angles. Need a tighter tolerance for non-touching shapes so
195        // that contacts are not missed.
196        let tolerance = if was_touching {
197            recycle_distance
198        } else {
199            recycle_distance_non_touching
200        };
201
202        if min_cos > crate::constants::CONTACT_RECYCLE_COS_ANGLE
203            && distance_ + max_extent * abs_float(qr.s) < tolerance
204        {
205            let dq_a = mul_rot(transform_a.q, invert_rot(cached_q_a));
206            let dq_b = mul_rot(transform_b.q, invert_rot(cached_q_b));
207            let normal = contact_sim.manifold.normal;
208
209            // Minimize round-off
210            let dc = sub_pos(body_sim_b.center, body_sim_a.center);
211
212            for i in 0..contact_sim.manifold.point_count as usize {
213                // Keep anchors but update separation, same as sub-stepping.
214                // This eliminates jitter.
215                let mp = &mut contact_sim.manifold.points[i];
216                let r_a = rotate_vector(dq_a, mp.anchor_a);
217                let r_b = rotate_vector(dq_b, mp.anchor_b);
218                let dp = crate::math_functions::add(dc, sub(r_b, r_a));
219                mp.separation = mp.base_separation + dot(dp, normal);
220                mp.persisted = true;
221            }
222
223            world.task_contexts[0].recycled_contact_count += 1;
224
225            // Contact is recycled. This also skips updating other aspects of
226            // the contact such as material parameters.
227            return;
228        }
229    }
230
231    // Caching for contact recycling.
232    contact_sim.cached_rotation_a = transform_a.q;
233    contact_sim.cached_rotation_b = transform_b.q;
234    contact_sim.cached_relative_pose = inv_mul_world_transforms(transform_a, transform_b);
235    contact_sim.sim_flags |= contact_flags::SIM_RELATIVE_TRANSFORM_VALID;
236
237    let center_offset_a = rotate_vector(transform_a.q, body_sim_a.local_center);
238    let center_offset_b = rotate_vector(transform_b.q, body_sim_b.local_center);
239
240    // This updates solid contacts
241    let touching = {
242        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
243        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
244        update_contact(
245            update_context,
246            contact_sim,
247            shape_a,
248            transform_a,
249            center_offset_a,
250            shape_b,
251            transform_b,
252            center_offset_b,
253        )
254    };
255
256    // State changes that affect island connectivity. Also affects contact
257    // events.
258    if touching && !was_touching {
259        contact_sim.sim_flags |= contact_flags::SIM_STARTED_TOUCHING;
260        world.task_contexts[0]
261            .contact_state_bit_set
262            .set_bit(contact_id as u32);
263    } else if !touching && was_touching {
264        contact_sim.sim_flags |= contact_flags::SIM_STOPPED_TOUCHING;
265        world.task_contexts[0]
266            .contact_state_bit_set
267            .set_bit(contact_id as u32);
268    }
269
270    for i in 0..contact_sim.manifold.point_count as usize {
271        let mp = &mut contact_sim.manifold.points[i];
272        mp.base_separation = mp.separation;
273    }
274}
275
276/// (static b2AddNonTouchingContact)
277fn add_non_touching_contact(world: &mut World, contact_id: i32, contact_sim: ContactSim) {
278    debug_assert!(world.contacts[contact_id as usize].set_index == AWAKE_SET);
279    let local_index = world.solver_sets[AWAKE_SET as usize].contact_sims.len() as i32;
280    {
281        let contact = &mut world.contacts[contact_id as usize];
282        contact.color_index = NULL_INDEX;
283        contact.local_index = local_index;
284    }
285    world.solver_sets[AWAKE_SET as usize]
286        .contact_sims
287        .push(contact_sim);
288}
289
290/// (static b2RemoveNonTouchingContact)
291fn remove_non_touching_contact(world: &mut World, set_index: i32, local_index: i32) {
292    let set = &mut world.solver_sets[set_index as usize];
293    let moved_index = set.contact_sims.len() as i32 - 1;
294    set.contact_sims.swap_remove(local_index as usize);
295    if moved_index != local_index {
296        let moved_contact_id =
297            world.solver_sets[set_index as usize].contact_sims[local_index as usize].contact_id;
298        let moved_contact = &mut world.contacts[moved_contact_id as usize];
299        debug_assert!(moved_contact.set_index == set_index);
300        debug_assert!(moved_contact.local_index == moved_index);
301        debug_assert!(moved_contact.color_index == NULL_INDEX);
302        moved_contact.local_index = local_index;
303    }
304}
305
306/// Narrow-phase collision. (b2Collide)
307pub(super) fn collide(world: &mut World, _context: &StepContext) {
308    // gather contacts into a single array for easier iteration; the order is
309    // graph colors then the awake set, matching the C gather. Size the buffer
310    // up front from the same counts the loops use, mirroring the C's single
311    // b2StackAlloc( contactCount * ... ) (physics_world.c b2Collide).
312    let mut contact_count = world.solver_sets[AWAKE_SET as usize].contact_sims.len();
313    for color_index in 0..GRAPH_COLOR_COUNT {
314        contact_count += world.constraint_graph.colors[color_index as usize]
315            .contact_sims
316            .len();
317    }
318    let mut locations: Vec<ContactSimLocation> = Vec::with_capacity(contact_count);
319    for color_index in 0..GRAPH_COLOR_COUNT {
320        let count = world.constraint_graph.colors[color_index as usize]
321            .contact_sims
322            .len();
323        for local_index in 0..count as i32 {
324            locations.push(ContactSimLocation {
325                color_index,
326                local_index,
327            });
328        }
329    }
330    {
331        let count = world.solver_sets[AWAKE_SET as usize].contact_sims.len();
332        for local_index in 0..count as i32 {
333            locations.push(ContactSimLocation {
334                color_index: NULL_INDEX,
335                local_index,
336            });
337        }
338    }
339
340    if locations.is_empty() {
341        return;
342    }
343
344    // Contact bit set on ids because contact locations are unstable as they
345    // move between touching and not touching.
346    let contact_id_capacity = world.contact_id_pool.id_capacity();
347    {
348        let task_context = &mut world.task_contexts[0];
349        task_context
350            .contact_state_bit_set
351            .set_bit_count_and_clear(contact_id_capacity as u32);
352        task_context.recycled_contact_count = 0;
353    }
354
355    // (b2CollideTask over the whole range on one worker)
356    let update_context = ContactUpdateContext::new(world);
357    for &location in &locations {
358        collide_contact(world, location, &update_context);
359    }
360
361    // Serially update contact state. Process contact state changes, iterating
362    // over set bits.
363    let end_event_array_index = world.end_event_array_index as usize;
364    let world_id = world.world_id;
365
366    let block_count = world.task_contexts[0].contact_state_bit_set.block_count();
367    for k in 0..block_count {
368        let mut bits = world.task_contexts[0].contact_state_bit_set.block(k);
369        while bits != 0 {
370            let ctz = bits.trailing_zeros();
371            let contact_id = (64 * k + ctz) as i32;
372
373            let (color_index, local_index, flags, generation, shape_id_a, shape_id_b) = {
374                let contact: &Contact = &world.contacts[contact_id as usize];
375                debug_assert!(contact.set_index == AWAKE_SET);
376                (
377                    contact.color_index,
378                    contact.local_index,
379                    contact.flags,
380                    contact.generation,
381                    contact.shape_id_a,
382                    contact.shape_id_b,
383                )
384            };
385
386            let sim_flags = if color_index != NULL_INDEX {
387                // contact lives in constraint graph
388                debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
389                world.constraint_graph.colors[color_index as usize].contact_sims
390                    [local_index as usize]
391                    .sim_flags
392            } else {
393                world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize].sim_flags
394            };
395
396            let (shape_id_a_full, shape_id_b_full) = {
397                let shape_a = &world.shapes[shape_id_a as usize];
398                let shape_b = &world.shapes[shape_id_b as usize];
399                (
400                    ShapeId {
401                        index1: shape_a.id + 1,
402                        world0: world_id,
403                        generation: shape_a.generation,
404                    },
405                    ShapeId {
406                        index1: shape_b.id + 1,
407                        world0: world_id,
408                        generation: shape_b.generation,
409                    },
410                )
411            };
412            let contact_full_id = ContactId {
413                index1: contact_id + 1,
414                world0: world_id,
415                padding: 0,
416                generation,
417            };
418
419            if sim_flags & contact_flags::SIM_DISJOINT != 0 {
420                // Bounding boxes no longer overlap
421                crate::contact::destroy_contact(world, contact_id, false);
422            } else if sim_flags & contact_flags::SIM_STARTED_TOUCHING != 0 {
423                debug_assert!(world.contacts[contact_id as usize].island_id == NULL_INDEX);
424
425                if flags & contact_flags::ENABLE_CONTACT_EVENTS != 0 {
426                    world.contact_begin_events.push(ContactBeginTouchEvent {
427                        shape_id_a: shape_id_a_full,
428                        shape_id_b: shape_id_b_full,
429                        contact_id: contact_full_id,
430                    });
431                }
432
433                debug_assert!(color_index == NULL_INDEX);
434                debug_assert!(
435                    world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize]
436                        .manifold
437                        .point_count
438                        > 0
439                );
440
441                // Link first because this wakes colliding bodies and ensures
442                // the body sims are in the correct place.
443                world.contacts[contact_id as usize].flags |= contact_flags::TOUCHING;
444                crate::island::link_contact(world, contact_id);
445
446                // Make sure these didn't change
447                debug_assert!(world.contacts[contact_id as usize].color_index == NULL_INDEX);
448                debug_assert!(world.contacts[contact_id as usize].local_index == local_index);
449
450                // Refresh the contact sim after the awake set may have grown
451                let mut contact_sim =
452                    world.solver_sets[AWAKE_SET as usize].contact_sims[local_index as usize];
453                contact_sim.sim_flags &= !contact_flags::SIM_STARTED_TOUCHING;
454
455                // Add first for memcpy
456                crate::constraint_graph::add_contact_to_graph(world, contact_sim, contact_id);
457
458                // This destroys the contact sim in the awake set
459                remove_non_touching_contact(world, AWAKE_SET, local_index);
460            } else if sim_flags & contact_flags::SIM_STOPPED_TOUCHING != 0 {
461                debug_assert!(color_index != NULL_INDEX);
462                let contact_sim = {
463                    let sim = &mut world.constraint_graph.colors[color_index as usize].contact_sims
464                        [local_index as usize];
465                    sim.sim_flags &= !contact_flags::SIM_STOPPED_TOUCHING;
466                    *sim
467                };
468                world.contacts[contact_id as usize].flags &= !contact_flags::TOUCHING;
469
470                if flags & contact_flags::ENABLE_CONTACT_EVENTS != 0 {
471                    world.contact_end_events[end_event_array_index].push(ContactEndTouchEvent {
472                        shape_id_a: shape_id_a_full,
473                        shape_id_b: shape_id_b_full,
474                        contact_id: contact_full_id,
475                    });
476                }
477
478                debug_assert!(contact_sim.manifold.point_count == 0);
479
480                crate::island::unlink_contact(world, contact_id);
481                let body_id_a = world.contacts[contact_id as usize].edges[0].body_id;
482                let body_id_b = world.contacts[contact_id as usize].edges[1].body_id;
483
484                // Add first for memcpy
485                add_non_touching_contact(world, contact_id, contact_sim);
486                crate::constraint_graph::remove_contact_from_graph(
487                    world,
488                    body_id_a,
489                    body_id_b,
490                    color_index,
491                    local_index,
492                );
493            }
494
495            // Clear the smallest set bit
496            bits &= bits - 1;
497        }
498    }
499
500    world.validate_solver_sets();
501    world.validate_contacts();
502}
503
504impl World {
505    /// (b2ValidateContacts — C compiles the body under B2_ENABLE_VALIDATION;
506    /// here the whole check runs in debug builds only)
507    pub fn validate_contacts(&self) {
508        if !cfg!(debug_assertions) {
509            return;
510        }
511
512        let contact_count = self.contacts.len() as i32;
513        debug_assert!(contact_count == self.contact_id_pool.id_capacity());
514        let mut allocated_contact_count = 0;
515
516        for contact_index in 0..contact_count {
517            let contact = &self.contacts[contact_index as usize];
518            if contact.contact_id == NULL_INDEX {
519                continue;
520            }
521
522            debug_assert!(contact.contact_id == contact_index);
523
524            allocated_contact_count += 1;
525
526            let touching = contact.flags & contact_flags::TOUCHING != 0;
527
528            let set_id = contact.set_index;
529
530            if set_id == AWAKE_SET {
531                if touching {
532                    debug_assert!(
533                        0 <= contact.color_index && contact.color_index < GRAPH_COLOR_COUNT
534                    );
535                } else {
536                    debug_assert!(contact.color_index == NULL_INDEX);
537                }
538            } else if set_id >= FIRST_SLEEPING_SET {
539                // Only touching contacts allowed in a sleeping set
540                debug_assert!(touching);
541            } else {
542                // Sleeping and non-touching contacts belong in the disabled set
543                debug_assert!(!touching && set_id == crate::solver_set::DISABLED_SET);
544            }
545
546            let contact_sim = if set_id == AWAKE_SET && contact.color_index != NULL_INDEX {
547                &self.constraint_graph.colors[contact.color_index as usize].contact_sims
548                    [contact.local_index as usize]
549            } else {
550                &self.solver_sets[set_id as usize].contact_sims[contact.local_index as usize]
551            };
552            debug_assert!(contact_sim.contact_id == contact_index);
553            debug_assert!(contact_sim.body_id_a == contact.edges[0].body_id);
554            debug_assert!(contact_sim.body_id_b == contact.edges[1].body_id);
555
556            let sim_touching = contact_sim.sim_flags & contact_flags::SIM_TOUCHING != 0;
557            debug_assert!(touching == sim_touching);
558
559            debug_assert!(
560                0 <= contact_sim.manifold.point_count && contact_sim.manifold.point_count <= 2
561            );
562        }
563
564        let contact_id_count = self.contact_id_pool.id_count();
565        debug_assert!(allocated_contact_count == contact_id_count);
566        let _ = allocated_contact_count;
567    }
568}