Skip to main content

box2d_rust/contact/
lifecycle.rs

1// Contact register table and contact creation/destruction from contact.c.
2//
3// SPDX-FileCopyrightText: 2023 Erin Catto
4// SPDX-License-Identifier: MIT
5
6use super::{contact_flags, Contact, ContactSim};
7use crate::body::body_flags;
8use crate::collision::{Manifold, ShapeType};
9use crate::constants::GRAPH_COLOR_COUNT;
10use crate::core::NULL_INDEX;
11use crate::distance::SimplexCache;
12use crate::events::ContactEndTouchEvent;
13use crate::id::{ContactId, ShapeId};
14use crate::solver_set::{AWAKE_SET, DISABLED_SET, STATIC_SET};
15use crate::table::shape_pair_key;
16use crate::world::World;
17
18/// The pairs registered with primary order by b2InitializeContactRegisters.
19fn is_primary_pair(type_a: ShapeType, type_b: ShapeType) -> bool {
20    use ShapeType::*;
21    matches!(
22        (type_a, type_b),
23        (Circle, Circle)
24            | (Capsule, Circle)
25            | (Capsule, Capsule)
26            | (Polygon, Circle)
27            | (Polygon, Capsule)
28            | (Polygon, Polygon)
29            | (Segment, Circle)
30            | (Segment, Capsule)
31            | (Segment, Polygon)
32            | (ChainSegment, Circle)
33            | (ChainSegment, Capsule)
34            | (ChainSegment, Polygon)
35    )
36}
37
38/// The C contact register table (s_registers) reduces to a match in Rust: for
39/// a shape type pair, is there a manifold function, and is (a, b) the primary
40/// (unflipped) order? (b2InitializeContactRegisters/b2AddType)
41pub(super) fn contact_register(type_a: ShapeType, type_b: ShapeType) -> Option<bool> {
42    if is_primary_pair(type_a, type_b) {
43        return Some(true);
44    }
45
46    // The flipped table entry (b2AddType registers type2/type1 with
47    // primary = false when the types differ).
48    if type_a != type_b && is_primary_pair(type_b, type_a) {
49        return Some(false);
50    }
51
52    None
53}
54
55/// (b2CanCollide)
56pub fn can_collide(type_a: ShapeType, type_b: ShapeType) -> bool {
57    contact_register(type_a, type_b).is_some()
58}
59
60/// WARNING: this should never fail to create a contact because the pair
61/// already exists in the pairSet. (b2CreateContact — C takes shape pointers;
62/// the Rust port takes shape ids.)
63pub fn create_contact(world: &mut World, shape_id_a: i32, shape_id_b: i32) {
64    let type_a = world.shapes[shape_id_a as usize].shape_type();
65    let type_b = world.shapes[shape_id_b as usize].shape_type();
66
67    let Some(primary) = contact_register(type_a, type_b) else {
68        // For example, no segment vs segment collision
69        return;
70    };
71
72    if !primary {
73        // flip order
74        create_contact(world, shape_id_b, shape_id_a);
75        return;
76    }
77
78    let body_id_a = world.shapes[shape_id_a as usize].body_id;
79    let body_id_b = world.shapes[shape_id_b as usize].body_id;
80
81    let set_a = world.bodies[body_id_a as usize].set_index;
82    let set_b = world.bodies[body_id_b as usize].set_index;
83    debug_assert!(set_a != DISABLED_SET && set_b != DISABLED_SET);
84    debug_assert!(set_a != STATIC_SET || set_b != STATIC_SET);
85
86    let set_index = if set_a == AWAKE_SET || set_b == AWAKE_SET {
87        AWAKE_SET
88    } else {
89        // sleeping and non-touching contacts live in the disabled set
90        // later if this set is found to be touching then the sleeping
91        // islands will be linked and the contact moved to the merged island
92        DISABLED_SET
93    };
94
95    // Create contact key and contact
96    let contact_id = world.contact_id_pool.alloc_id();
97    if contact_id == world.contacts.len() as i32 {
98        world.contacts.push(Contact::default());
99    }
100
101    let local_index = world.solver_sets[set_index as usize].contact_sims.len() as i32;
102
103    {
104        let contact = &mut world.contacts[contact_id as usize];
105        contact.contact_id = contact_id;
106        contact.generation = contact.generation.wrapping_add(1);
107        contact.set_index = set_index;
108        contact.color_index = NULL_INDEX;
109        contact.local_index = local_index;
110        contact.island_id = NULL_INDEX;
111        contact.island_index = NULL_INDEX;
112        contact.shape_id_a = shape_id_a;
113        contact.shape_id_b = shape_id_b;
114        contact.flags = 0;
115    }
116
117    // Both bodies must enable recycling
118    if (world.bodies[body_id_a as usize].flags & body_flags::BODY_ENABLE_CONTACT_RECYCLING) != 0
119        && (world.bodies[body_id_b as usize].flags & body_flags::BODY_ENABLE_CONTACT_RECYCLING) != 0
120    {
121        world.contacts[contact_id as usize].flags |= contact_flags::RECYCLE;
122    }
123
124    debug_assert!(
125        world.shapes[shape_id_a as usize].sensor_index == NULL_INDEX
126            && world.shapes[shape_id_b as usize].sensor_index == NULL_INDEX
127    );
128
129    if world.shapes[shape_id_a as usize].enable_contact_events
130        || world.shapes[shape_id_b as usize].enable_contact_events
131    {
132        world.contacts[contact_id as usize].flags |= contact_flags::ENABLE_CONTACT_EVENTS;
133    }
134
135    // Connect to body A
136    {
137        let head_contact_key = world.bodies[body_id_a as usize].head_contact_key;
138        {
139            let contact = &mut world.contacts[contact_id as usize];
140            contact.edges[0].body_id = body_id_a;
141            contact.edges[0].prev_key = NULL_INDEX;
142            contact.edges[0].next_key = head_contact_key;
143        }
144
145        let key_a = contact_id << 1;
146        if head_contact_key != NULL_INDEX {
147            let head_contact = &mut world.contacts[(head_contact_key >> 1) as usize];
148            head_contact.edges[(head_contact_key & 1) as usize].prev_key = key_a;
149        }
150        let body_a = &mut world.bodies[body_id_a as usize];
151        body_a.head_contact_key = key_a;
152        body_a.contact_count += 1;
153    }
154
155    // Connect to body B
156    {
157        let head_contact_key = world.bodies[body_id_b as usize].head_contact_key;
158        {
159            let contact = &mut world.contacts[contact_id as usize];
160            contact.edges[1].body_id = body_id_b;
161            contact.edges[1].prev_key = NULL_INDEX;
162            contact.edges[1].next_key = head_contact_key;
163        }
164
165        let key_b = (contact_id << 1) | 1;
166        if head_contact_key != NULL_INDEX {
167            let head_contact = &mut world.contacts[(head_contact_key >> 1) as usize];
168            head_contact.edges[(head_contact_key & 1) as usize].prev_key = key_b;
169        }
170        let body_b = &mut world.bodies[body_id_b as usize];
171        body_b.head_contact_key = key_b;
172        body_b.contact_count += 1;
173    }
174
175    // Add to pair set for fast lookup.
176    let pair_key = shape_pair_key(shape_id_a, shape_id_b);
177    world.broad_phase.pair_set.add_key(pair_key);
178
179    // Contacts are created as non-touching. Later if they are found to be
180    // touching they will link islands and be moved into the constraint graph.
181    let contact_flags_now = world.contacts[contact_id as usize].flags;
182    let shape_a = &world.shapes[shape_id_a as usize];
183    let shape_b = &world.shapes[shape_id_b as usize];
184
185    let mut contact_sim = ContactSim {
186        contact_id,
187        // C: #if B2_ENABLE_VALIDATION — always present in the Rust port
188        body_id_a,
189        body_id_b,
190        body_sim_index_a: NULL_INDEX,
191        body_sim_index_b: NULL_INDEX,
192        inv_mass_a: 0.0,
193        inv_i_a: 0.0,
194        inv_mass_b: 0.0,
195        inv_i_b: 0.0,
196        shape_id_a,
197        shape_id_b,
198        cache: SimplexCache::default(),
199        manifold: Manifold::default(),
200        // These get updated in the narrow phase, but these are needed for
201        // first touch
202        friction: (world.friction_callback.unwrap())(
203            shape_a.material.friction,
204            shape_a.material.user_material_id,
205            shape_b.material.friction,
206            shape_b.material.user_material_id,
207        ),
208        restitution: (world.restitution_callback.unwrap())(
209            shape_a.material.restitution,
210            shape_a.material.user_material_id,
211            shape_b.material.restitution,
212            shape_b.material.user_material_id,
213        ),
214        tangent_speed: 0.0,
215        sim_flags: contact_flags_now,
216        ..ContactSim::default()
217    };
218
219    if shape_a.enable_pre_solve_events || shape_b.enable_pre_solve_events {
220        contact_sim.sim_flags |= contact_flags::SIM_ENABLE_PRE_SOLVE_EVENTS;
221    }
222
223    world.solver_sets[set_index as usize]
224        .contact_sims
225        .push(contact_sim);
226}
227
228/// A contact is destroyed when:
229/// - broad-phase proxies stop overlapping
230/// - a body is destroyed
231/// - a body is disabled
232/// - a body changes type from dynamic to kinematic or static
233/// - a shape is destroyed
234/// - contact filtering is modified
235///
236/// (b2DestroyContact — C takes a contact pointer; the Rust port takes the id.)
237pub fn destroy_contact(world: &mut World, contact_id: i32, wake_bodies: bool) {
238    let (shape_id_a, shape_id_b, edge_a, edge_b, flags, generation) = {
239        let contact = &world.contacts[contact_id as usize];
240        (
241            contact.shape_id_a,
242            contact.shape_id_b,
243            contact.edges[0],
244            contact.edges[1],
245            contact.flags,
246            contact.generation,
247        )
248    };
249
250    // Remove pair from set
251    let pair_key = shape_pair_key(shape_id_a, shape_id_b);
252    world.broad_phase.pair_set.remove_key(pair_key);
253
254    let body_id_a = edge_a.body_id;
255    let body_id_b = edge_b.body_id;
256
257    let touching = (flags & contact_flags::TOUCHING) != 0;
258
259    // End touch event
260    if touching && (flags & contact_flags::ENABLE_CONTACT_EVENTS) != 0 {
261        let world_id = world.world_id;
262        let shape_a = &world.shapes[shape_id_a as usize];
263        let shape_b = &world.shapes[shape_id_b as usize];
264
265        let event = ContactEndTouchEvent {
266            shape_id_a: ShapeId {
267                index1: shape_a.id + 1,
268                world0: world_id,
269                generation: shape_a.generation,
270            },
271            shape_id_b: ShapeId {
272                index1: shape_b.id + 1,
273                world0: world_id,
274                generation: shape_b.generation,
275            },
276            contact_id: ContactId {
277                index1: contact_id + 1,
278                world0: world_id,
279                padding: 0,
280                generation,
281            },
282        };
283
284        world.contact_end_events[world.end_event_array_index as usize].push(event);
285    }
286
287    // Remove from body A
288    if edge_a.prev_key != NULL_INDEX {
289        let prev_contact = &mut world.contacts[(edge_a.prev_key >> 1) as usize];
290        prev_contact.edges[(edge_a.prev_key & 1) as usize].next_key = edge_a.next_key;
291    }
292
293    if edge_a.next_key != NULL_INDEX {
294        let next_contact = &mut world.contacts[(edge_a.next_key >> 1) as usize];
295        next_contact.edges[(edge_a.next_key & 1) as usize].prev_key = edge_a.prev_key;
296    }
297
298    let edge_key_a = contact_id << 1;
299    {
300        let body_a = &mut world.bodies[body_id_a as usize];
301        if body_a.head_contact_key == edge_key_a {
302            body_a.head_contact_key = edge_a.next_key;
303        }
304        body_a.contact_count -= 1;
305    }
306
307    // Remove from body B
308    if edge_b.prev_key != NULL_INDEX {
309        let prev_contact = &mut world.contacts[(edge_b.prev_key >> 1) as usize];
310        prev_contact.edges[(edge_b.prev_key & 1) as usize].next_key = edge_b.next_key;
311    }
312
313    if edge_b.next_key != NULL_INDEX {
314        let next_contact = &mut world.contacts[(edge_b.next_key >> 1) as usize];
315        next_contact.edges[(edge_b.next_key & 1) as usize].prev_key = edge_b.prev_key;
316    }
317
318    let edge_key_b = (contact_id << 1) | 1;
319    {
320        let body_b = &mut world.bodies[body_id_b as usize];
321        if body_b.head_contact_key == edge_key_b {
322            body_b.head_contact_key = edge_b.next_key;
323        }
324        body_b.contact_count -= 1;
325    }
326
327    // Remove contact from the array that owns it
328    if world.contacts[contact_id as usize].island_id != NULL_INDEX {
329        crate::island::unlink_contact(world, contact_id);
330    }
331
332    let (color_index, local_index, set_index) = {
333        let contact = &world.contacts[contact_id as usize];
334        (contact.color_index, contact.local_index, contact.set_index)
335    };
336    if color_index != NULL_INDEX {
337        // contact is an active constraint
338        debug_assert!(set_index == AWAKE_SET);
339        crate::constraint_graph::remove_contact_from_graph(
340            world,
341            body_id_a,
342            body_id_b,
343            color_index,
344            local_index,
345        );
346    } else {
347        // contact is non-touching or is sleeping
348        debug_assert!(
349            set_index != AWAKE_SET
350                || (world.contacts[contact_id as usize].flags & contact_flags::TOUCHING) == 0
351        );
352        let set = &mut world.solver_sets[set_index as usize];
353        let moved_index = set.contact_sims.len() as i32 - 1;
354        set.contact_sims.swap_remove(local_index as usize);
355        if moved_index != local_index {
356            let moved_contact_id =
357                world.solver_sets[set_index as usize].contact_sims[local_index as usize].contact_id;
358            world.contacts[moved_contact_id as usize].local_index = local_index;
359        }
360    }
361
362    // Free contact and id (preserve generation)
363    {
364        let contact = &mut world.contacts[contact_id as usize];
365        contact.contact_id = NULL_INDEX;
366        contact.set_index = NULL_INDEX;
367        contact.color_index = NULL_INDEX;
368        contact.local_index = NULL_INDEX;
369    }
370    world.contact_id_pool.free_id(contact_id);
371
372    if wake_bodies && touching {
373        crate::body::wake_body(world, body_id_a);
374        crate::body::wake_body(world, body_id_b);
375    }
376}
377
378/// Validate a ContactId and return the raw contact index. (b2GetContactFullId
379/// — C returns a pointer; Rust returns the index into `world.contacts`)
380pub fn get_contact_full_id(world: &World, contact_id: crate::id::ContactId) -> i32 {
381    let id = contact_id.index1 - 1;
382    debug_assert!((id as usize) < world.contacts.len());
383    let contact = &world.contacts[id as usize];
384    debug_assert!(contact.contact_id == id && contact.generation == contact_id.generation);
385    id
386}
387
388/// Borrow a contact's sim data mutably: constraint graph color for awake
389/// touching contacts, otherwise the owning solver set. (b2GetContactSim)
390pub fn get_contact_sim(world: &mut World, contact_id: i32) -> &mut ContactSim {
391    let (set_index, color_index, local_index) = {
392        let contact = &world.contacts[contact_id as usize];
393        (contact.set_index, contact.color_index, contact.local_index)
394    };
395
396    if set_index == AWAKE_SET && color_index != NULL_INDEX {
397        // contact lives in constraint graph
398        debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
399        &mut world.constraint_graph.colors[color_index as usize].contact_sims[local_index as usize]
400    } else {
401        &mut world.solver_sets[set_index as usize].contact_sims[local_index as usize]
402    }
403}
404
405/// Shared-reference variant of get_contact_sim for read-only accessors. (The C
406/// b2GetContactSim is used for both; Rust needs the split.)
407pub fn get_contact_sim_ref(world: &World, contact_id: i32) -> &ContactSim {
408    let contact = &world.contacts[contact_id as usize];
409
410    if contact.set_index == AWAKE_SET && contact.color_index != NULL_INDEX {
411        debug_assert!((0..GRAPH_COLOR_COUNT).contains(&contact.color_index));
412        &world.constraint_graph.colors[contact.color_index as usize].contact_sims
413            [contact.local_index as usize]
414    } else {
415        &world.solver_sets[contact.set_index as usize].contact_sims[contact.local_index as usize]
416    }
417}