Skip to main content

box2d_rust/contact/
update.rs

1// Narrow-phase contact update from contact.c: manifold dispatch and
2// b2UpdateContact.
3//
4// SPDX-FileCopyrightText: 2023 Erin Catto
5// SPDX-License-Identifier: MIT
6
7use super::{contact_flags, ContactSim};
8use crate::collision::LocalManifold;
9use crate::collision::Manifold;
10use crate::constants::linear_slop;
11use crate::distance::SimplexCache;
12use crate::id::ShapeId;
13use crate::manifold::{
14    collide_capsule_and_circle, collide_capsules, collide_chain_segment_and_capsule,
15    collide_chain_segment_and_circle, collide_chain_segment_and_polygon, collide_circles,
16    collide_polygon_and_capsule, collide_polygon_and_circle, collide_polygons,
17    collide_segment_and_capsule, collide_segment_and_circle, collide_segment_and_polygon,
18};
19use crate::math_functions::{
20    add, inv_mul_world_transforms, max_float, offset_pos, rotate_vector, sub, sub_pos, Transform,
21    Vec2, WorldTransform,
22};
23use crate::shape::Shape;
24use crate::types::{FrictionCallback, RestitutionCallback};
25use crate::world::{PreSolveFcn, World};
26
27/// Manifold function dispatch over the primary shape pair. The caller must
28/// pass shapes in primary order (create_contact guarantees this).
29/// (b2ManifoldFcn dispatch through s_registers)
30fn compute_manifold(
31    shape_a: &Shape,
32    shape_b: &Shape,
33    xf: Transform,
34    cache: &mut SimplexCache,
35) -> LocalManifold {
36    use crate::collision::ShapeGeometry;
37    match (&shape_a.geometry, &shape_b.geometry) {
38        (ShapeGeometry::Circle(a), ShapeGeometry::Circle(b)) => collide_circles(a, b, xf),
39        (ShapeGeometry::Capsule(a), ShapeGeometry::Circle(b)) => {
40            collide_capsule_and_circle(a, b, xf)
41        }
42        (ShapeGeometry::Capsule(a), ShapeGeometry::Capsule(b)) => collide_capsules(a, b, xf),
43        (ShapeGeometry::Polygon(a), ShapeGeometry::Circle(b)) => {
44            collide_polygon_and_circle(a, b, xf)
45        }
46        (ShapeGeometry::Polygon(a), ShapeGeometry::Capsule(b)) => {
47            collide_polygon_and_capsule(a, b, xf)
48        }
49        (ShapeGeometry::Polygon(a), ShapeGeometry::Polygon(b)) => collide_polygons(a, b, xf),
50        (ShapeGeometry::Segment(a), ShapeGeometry::Circle(b)) => {
51            collide_segment_and_circle(a, b, xf)
52        }
53        (ShapeGeometry::Segment(a), ShapeGeometry::Capsule(b)) => {
54            collide_segment_and_capsule(a, b, xf)
55        }
56        (ShapeGeometry::Segment(a), ShapeGeometry::Polygon(b)) => {
57            collide_segment_and_polygon(a, b, xf)
58        }
59        (ShapeGeometry::ChainSegment(a), ShapeGeometry::Circle(b)) => {
60            collide_chain_segment_and_circle(a, b, xf)
61        }
62        (ShapeGeometry::ChainSegment(a), ShapeGeometry::Capsule(b)) => {
63            collide_chain_segment_and_capsule(a, b, xf, cache)
64        }
65        (ShapeGeometry::ChainSegment(a), ShapeGeometry::Polygon(b)) => {
66            collide_chain_segment_and_polygon(a, b, xf, cache)
67        }
68        // Contacts are only created for registered primary pairs, so this is
69        // unreachable (the C equivalent would call a null function pointer).
70        _ => unreachable!("no manifold function for this shape pair"),
71    }
72}
73
74/// The world data update_contact needs, copied out so the caller can hold
75/// `&mut ContactSim` and `&Shape` borrows at the same time (the C version
76/// reads these through b2World*).
77#[derive(Clone, Copy)]
78pub struct ContactUpdateContext {
79    pub friction_callback: FrictionCallback,
80    pub restitution_callback: RestitutionCallback,
81    pub pre_solve_fcn: Option<PreSolveFcn>,
82    pub pre_solve_context: u64,
83    pub world_id: u16,
84    pub enable_speculative: bool,
85}
86
87impl ContactUpdateContext {
88    pub fn new(world: &World) -> ContactUpdateContext {
89        ContactUpdateContext {
90            friction_callback: world.friction_callback.unwrap(),
91            restitution_callback: world.restitution_callback.unwrap(),
92            pre_solve_fcn: world.pre_solve_fcn,
93            pre_solve_context: world.pre_solve_context,
94            world_id: world.world_id,
95            enable_speculative: world.enable_speculative,
96        }
97    }
98}
99
100/// Update the contact manifold and touching status.
101/// Note: do not assume the shape AABBs are overlapping or are valid.
102/// (b2UpdateContact)
103#[allow(clippy::too_many_arguments)]
104pub fn update_contact(
105    ctx: &ContactUpdateContext,
106    contact_sim: &mut ContactSim,
107    shape_a: &Shape,
108    transform_a: WorldTransform,
109    center_offset_a: Vec2,
110    shape_b: &Shape,
111    transform_b: WorldTransform,
112    center_offset_b: Vec2,
113) -> bool {
114    // Save old manifold
115    let mut old_manifold = contact_sim.manifold;
116
117    // Compute the manifold in frame A, then marshal it to world anchors
118    // relative to each shape origin. The relative pose differences the two
119    // world positions before the narrow phase runs in frame A, so precision is
120    // retained far from the origin.
121    // anchorB = worldPoint - pB = rot(qA, localAnchorA) + pA - pB = anchorA + (pA - pB)
122    let relative_transform = inv_mul_world_transforms(transform_a, transform_b);
123    let local = compute_manifold(shape_a, shape_b, relative_transform, &mut contact_sim.cache);
124
125    contact_sim.manifold = Manifold::default();
126    contact_sim.manifold.normal = rotate_vector(transform_a.q, local.normal);
127    contact_sim.manifold.point_count = local.point_count;
128
129    let origin_delta = sub_pos(transform_a.p, transform_b.p);
130    for i in 0..local.point_count as usize {
131        let mp = &mut contact_sim.manifold.points[i];
132        mp.anchor_a = rotate_vector(transform_a.q, local.points[i].point);
133        mp.anchor_b = add(mp.anchor_a, origin_delta);
134        mp.separation = local.points[i].separation;
135        mp.id = local.points[i].id;
136    }
137
138    // Keep these updated in case the values on the shapes are modified
139    contact_sim.friction = (ctx.friction_callback)(
140        shape_a.material.friction,
141        shape_a.material.user_material_id,
142        shape_b.material.friction,
143        shape_b.material.user_material_id,
144    );
145    contact_sim.restitution = (ctx.restitution_callback)(
146        shape_a.material.restitution,
147        shape_a.material.user_material_id,
148        shape_b.material.restitution,
149        shape_b.material.user_material_id,
150    );
151
152    if shape_a.material.rolling_resistance > 0.0 || shape_b.material.rolling_resistance > 0.0 {
153        let radius_a = shape_a.radius();
154        let radius_b = shape_b.radius();
155        let max_radius = max_float(radius_a, radius_b);
156        contact_sim.rolling_resistance = max_float(
157            shape_a.material.rolling_resistance,
158            shape_b.material.rolling_resistance,
159        ) * max_radius;
160    } else {
161        contact_sim.rolling_resistance = 0.0;
162    }
163
164    contact_sim.tangent_speed = shape_a.material.tangent_speed + shape_b.material.tangent_speed;
165
166    let mut point_count = contact_sim.manifold.point_count;
167    let mut touching = point_count > 0;
168
169    if let (true, Some(pre_solve_fcn), true) = (
170        touching,
171        ctx.pre_solve_fcn,
172        (contact_sim.sim_flags & contact_flags::SIM_ENABLE_PRE_SOLVE_EVENTS) != 0,
173    ) {
174        let shape_id_a = ShapeId {
175            index1: shape_a.id + 1,
176            world0: ctx.world_id,
177            generation: shape_a.generation,
178        };
179        let shape_id_b = ShapeId {
180            index1: shape_b.id + 1,
181            world0: ctx.world_id,
182            generation: shape_b.generation,
183        };
184
185        let manifold = &contact_sim.manifold;
186        let mut best_separation = manifold.points[0].separation;
187        let mut best_point = offset_pos(transform_a.p, manifold.points[0].anchor_a);
188
189        // Get deepest point
190        for i in 1..manifold.point_count as usize {
191            let separation = manifold.points[i].separation;
192            if separation < best_separation {
193                best_separation = separation;
194                best_point = offset_pos(transform_a.p, manifold.points[i].anchor_a);
195            }
196        }
197
198        // this call assumes thread safety
199        touching = pre_solve_fcn(
200            shape_id_a,
201            shape_id_b,
202            best_point,
203            manifold.normal,
204            ctx.pre_solve_context,
205        );
206        if !touching {
207            // disable contact
208            point_count = 0;
209            contact_sim.manifold.point_count = 0;
210        }
211    }
212
213    // This flag is for testing
214    if !ctx.enable_speculative && point_count == 2 {
215        if contact_sim.manifold.points[0].separation > 1.5 * linear_slop() {
216            contact_sim.manifold.points[0] = contact_sim.manifold.points[1];
217            contact_sim.manifold.point_count = 1;
218        } else if contact_sim.manifold.points[1].separation > 1.5 * linear_slop() {
219            contact_sim.manifold.point_count = 1;
220        }
221
222        point_count = contact_sim.manifold.point_count;
223    }
224
225    if touching && (shape_a.enable_hit_events || shape_b.enable_hit_events) {
226        contact_sim.sim_flags |= contact_flags::SIM_ENABLE_HIT_EVENT;
227    } else {
228        contact_sim.sim_flags &= !contact_flags::SIM_ENABLE_HIT_EVENT;
229    }
230
231    if point_count > 0 {
232        contact_sim.manifold.rolling_impulse = old_manifold.rolling_impulse;
233    }
234
235    // Match old contact ids to new contact ids and copy the
236    // stored impulses to warm start the solver.
237    let mut unmatched_count = 0;
238    for i in 0..point_count as usize {
239        let mp2 = &mut contact_sim.manifold.points[i];
240
241        // shift anchors to be center of mass relative
242        mp2.anchor_a = sub(mp2.anchor_a, center_offset_a);
243        mp2.anchor_b = sub(mp2.anchor_b, center_offset_b);
244
245        mp2.normal_impulse = 0.0;
246        mp2.tangent_impulse = 0.0;
247        mp2.total_normal_impulse = 0.0;
248        mp2.normal_velocity = 0.0;
249        mp2.persisted = false;
250
251        let id2 = mp2.id;
252
253        for j in 0..old_manifold.point_count as usize {
254            let mp1 = &mut old_manifold.points[j];
255
256            if mp1.id == id2 {
257                mp2.normal_impulse = mp1.normal_impulse;
258                mp2.tangent_impulse = mp1.tangent_impulse;
259                mp2.persisted = true;
260
261                // clear old impulse
262                mp1.normal_impulse = 0.0;
263                mp1.tangent_impulse = 0.0;
264                break;
265            }
266        }
267
268        unmatched_count += if mp2.persisted { 0 } else { 1 };
269    }
270
271    // The C `#if 0` block distributing unmatched impulses is not ported.
272    let _ = unmatched_count;
273
274    if touching {
275        contact_sim.sim_flags |= contact_flags::SIM_TOUCHING;
276    } else {
277        contact_sim.sim_flags &= !contact_flags::SIM_TOUCHING;
278    }
279
280    touching
281}