Skip to main content

box2d_rust/
contact_solver.rs

1// Port of contact_solver.h/.c: the scalar contact constraint kernels.
2//
3// The C file has scalar "overflow" kernels plus SIMD "wide" kernels used for
4// graph-color contacts (with a per-lane scalar emulation when no SIMD target
5// is enabled). The wide kernels compute exactly the same per-contact float
6// sequence as the scalar kernels because bodies within a color are disjoint,
7// so this serial port routes every color through the scalar kernels; the
8// solver iterates colors in order (overflow last, matching the C stage
9// layout).
10//
11// The C stores per-color constraint arrays in arena scratch pointed to by
12// b2GraphColor; the Rust solve pass owns them as local Vecs and passes
13// parallel slices in.
14//
15// contact separation for sub-stepping
16// s = s0 + dot(cB + rB - cA - rA, normal)
17// normal is held constant
18// body positions c can translate and anchors r can rotate
19// s(t) = s0 + dot(cB(t) + rB(t) - cA(t) - rA(t), normal)
20// s(t) = s0 + dot(cB0 + dpB + rot(dqB, rB0) - cA0 - dpA - rot(dqA, rA0), normal)
21// s(t) = s0 + dot(cB0 - cA0, normal) + dot(dpB - dpA + rot(dqB, rB0) - rot(dqA, rA0), normal)
22// s_base = s0 + dot(cB0 - cA0, normal)
23//
24// SPDX-FileCopyrightText: 2023 Erin Catto
25// SPDX-License-Identifier: MIT
26//
27// bring-up: called by the solver slice.
28
29use crate::body::{body_flags, BodyState, IDENTITY_BODY_STATE};
30use crate::contact::ContactSim;
31use crate::core::NULL_INDEX;
32use crate::math_functions::{
33    add, clamp_float, cross, cross_sv, dot, max_float, mul_add, mul_sub, mul_sv, right_perp,
34    rotate_vector, sub, Vec2, VEC2_ZERO,
35};
36use crate::solver::{Softness, StepContext};
37
38/// (b2ContactConstraintPoint)
39#[derive(Debug, Clone, Copy, PartialEq, Default)]
40pub struct ContactConstraintPoint {
41    pub anchor_a: Vec2,
42    pub anchor_b: Vec2,
43    pub base_separation: f32,
44    pub relative_velocity: f32,
45    pub normal_impulse: f32,
46    pub tangent_impulse: f32,
47    pub total_normal_impulse: f32,
48    pub normal_mass: f32,
49    pub tangent_mass: f32,
50}
51
52/// (b2ContactConstraint)
53#[derive(Debug, Clone, Copy, PartialEq, Default)]
54pub struct ContactConstraint {
55    /// base-1, 0 for null
56    pub index_a: i32,
57    pub index_b: i32,
58    pub points: [ContactConstraintPoint; 2],
59    pub normal: Vec2,
60    pub inv_mass_a: f32,
61    pub inv_mass_b: f32,
62    pub inv_i_a: f32,
63    pub inv_i_b: f32,
64    pub friction: f32,
65    pub restitution: f32,
66    pub tangent_speed: f32,
67    pub rolling_resistance: f32,
68    pub rolling_mass: f32,
69    pub rolling_impulse: f32,
70    pub softness: Softness,
71    pub point_count: i32,
72}
73
74/// Build the constraints for one color's touching contacts. The constraints
75/// slice is parallel to the contacts slice.
76/// (b2PrepareContacts_Overflow / per-lane b2PrepareContactsTask)
77pub fn prepare_contacts(
78    constraints: &mut [ContactConstraint],
79    contacts: &[ContactSim],
80    states: &[BodyState],
81    context: &StepContext,
82) {
83    debug_assert!(constraints.len() == contacts.len());
84
85    // Stiffer for static contacts to avoid bodies getting pushed through the
86    // ground
87    let contact_softness = context.contact_softness;
88    let static_softness = context.static_softness;
89
90    let warm_start_scale = if context.enable_warm_starting {
91        1.0
92    } else {
93        0.0
94    };
95
96    for (constraint, contact_sim) in constraints.iter_mut().zip(contacts.iter()) {
97        let manifold = &contact_sim.manifold;
98        let point_count = manifold.point_count;
99
100        debug_assert!(0 < point_count && point_count <= 2);
101
102        let index_a = contact_sim.body_sim_index_a;
103        let index_b = contact_sim.body_sim_index_b;
104
105        // 0 is null
106        constraint.index_a = index_a + 1;
107        constraint.index_b = index_b + 1;
108        constraint.normal = manifold.normal;
109        constraint.friction = contact_sim.friction;
110        constraint.restitution = contact_sim.restitution;
111        constraint.rolling_resistance = contact_sim.rolling_resistance;
112        constraint.rolling_impulse = warm_start_scale * manifold.rolling_impulse;
113        constraint.tangent_speed = contact_sim.tangent_speed;
114        constraint.point_count = point_count;
115
116        let mut v_a = VEC2_ZERO;
117        let mut w_a = 0.0;
118        let m_a = contact_sim.inv_mass_a;
119        let i_a = contact_sim.inv_i_a;
120        if index_a != NULL_INDEX {
121            let state_a = &states[index_a as usize];
122            v_a = state_a.linear_velocity;
123            w_a = state_a.angular_velocity;
124        }
125
126        let mut v_b = VEC2_ZERO;
127        let mut w_b = 0.0;
128        let m_b = contact_sim.inv_mass_b;
129        let i_b = contact_sim.inv_i_b;
130        if index_b != NULL_INDEX {
131            let state_b = &states[index_b as usize];
132            v_b = state_b.linear_velocity;
133            w_b = state_b.angular_velocity;
134        }
135
136        if index_a == NULL_INDEX || index_b == NULL_INDEX {
137            constraint.softness = static_softness;
138        } else {
139            constraint.softness = contact_softness;
140        }
141
142        // copy mass into constraint to avoid cache misses during sub-stepping
143        constraint.inv_mass_a = m_a;
144        constraint.inv_i_a = i_a;
145        constraint.inv_mass_b = m_b;
146        constraint.inv_i_b = i_b;
147
148        {
149            let k = i_a + i_b;
150            constraint.rolling_mass = if k > 0.0 { 1.0 / k } else { 0.0 };
151        }
152
153        let normal = constraint.normal;
154        let tangent = right_perp(constraint.normal);
155
156        for j in 0..point_count as usize {
157            let mp = &manifold.points[j];
158            let cp = &mut constraint.points[j];
159
160            cp.normal_impulse = warm_start_scale * mp.normal_impulse;
161            cp.tangent_impulse = warm_start_scale * mp.tangent_impulse;
162            cp.total_normal_impulse = 0.0;
163
164            let r_a = mp.anchor_a;
165            let r_b = mp.anchor_b;
166
167            cp.anchor_a = r_a;
168            cp.anchor_b = r_b;
169            cp.base_separation = mp.separation - dot(sub(r_b, r_a), normal);
170
171            let rn_a = cross(r_a, normal);
172            let rn_b = cross(r_b, normal);
173            let k_normal = m_a + m_b + i_a * rn_a * rn_a + i_b * rn_b * rn_b;
174            cp.normal_mass = if k_normal > 0.0 { 1.0 / k_normal } else { 0.0 };
175
176            let rt_a = cross(r_a, tangent);
177            let rt_b = cross(r_b, tangent);
178            let k_tangent = m_a + m_b + i_a * rt_a * rt_a + i_b * rt_b * rt_b;
179            cp.tangent_mass = if k_tangent > 0.0 {
180                1.0 / k_tangent
181            } else {
182                0.0
183            };
184
185            // Save relative velocity for restitution
186            let vr_a = add(v_a, cross_sv(w_a, r_a));
187            let vr_b = add(v_b, cross_sv(w_b, r_b));
188            cp.relative_velocity = dot(normal, sub(vr_b, vr_a));
189        }
190    }
191}
192
193/// (b2WarmStartContacts_Overflow / per-lane b2WarmStartContactsTask)
194pub fn warm_start_contacts(constraints: &mut [ContactConstraint], states: &mut [BodyState]) {
195    for constraint in constraints.iter_mut() {
196        let index_a = constraint.index_a - 1;
197        let index_b = constraint.index_b - 1;
198
199        // This is a dummy state to represent a static body because static
200        // bodies don't have a solver body.
201        let mut state_a = if index_a == NULL_INDEX {
202            IDENTITY_BODY_STATE
203        } else {
204            states[index_a as usize]
205        };
206        let mut state_b = if index_b == NULL_INDEX {
207            IDENTITY_BODY_STATE
208        } else {
209            states[index_b as usize]
210        };
211
212        let mut v_a = state_a.linear_velocity;
213        let mut w_a = state_a.angular_velocity;
214        let mut v_b = state_b.linear_velocity;
215        let mut w_b = state_b.angular_velocity;
216
217        let m_a = constraint.inv_mass_a;
218        let i_a = constraint.inv_i_a;
219        let m_b = constraint.inv_mass_b;
220        let i_b = constraint.inv_i_b;
221
222        let normal = constraint.normal;
223        let tangent = right_perp(constraint.normal);
224        let point_count = constraint.point_count;
225
226        for j in 0..point_count as usize {
227            let cp = &mut constraint.points[j];
228
229            // fixed anchors
230            let r_a = cp.anchor_a;
231            let r_b = cp.anchor_b;
232
233            let p = add(
234                mul_sv(cp.normal_impulse, normal),
235                mul_sv(cp.tangent_impulse, tangent),
236            );
237
238            cp.total_normal_impulse += cp.normal_impulse;
239
240            w_a -= i_a * cross(r_a, p);
241            v_a = mul_add(v_a, -m_a, p);
242            w_b += i_b * cross(r_b, p);
243            v_b = mul_add(v_b, m_b, p);
244        }
245
246        w_a -= i_a * constraint.rolling_impulse;
247        w_b += i_b * constraint.rolling_impulse;
248
249        if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
250            state_a.linear_velocity = v_a;
251            state_a.angular_velocity = w_a;
252            states[index_a as usize] = state_a;
253        }
254
255        if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
256            state_b.linear_velocity = v_b;
257            state_b.angular_velocity = w_b;
258            states[index_b as usize] = state_b;
259        }
260    }
261}
262
263/// (b2SolveContacts_Overflow / per-lane b2SolveContactsTask)
264pub fn solve_contacts(
265    constraints: &mut [ContactConstraint],
266    states: &mut [BodyState],
267    context: &StepContext,
268    use_bias: bool,
269) {
270    let inv_h = context.inv_h;
271    let contact_speed = context.contact_speed;
272
273    for constraint in constraints.iter_mut() {
274        let m_a = constraint.inv_mass_a;
275        let i_a = constraint.inv_i_a;
276        let m_b = constraint.inv_mass_b;
277        let i_b = constraint.inv_i_b;
278
279        let index_a = constraint.index_a - 1;
280        let index_b = constraint.index_b - 1;
281
282        // This is a dummy body to represent a static body since static bodies
283        // don't have a solver body.
284        let mut state_a = if index_a == NULL_INDEX {
285            IDENTITY_BODY_STATE
286        } else {
287            states[index_a as usize]
288        };
289        let mut v_a = state_a.linear_velocity;
290        let mut w_a = state_a.angular_velocity;
291        let dq_a = state_a.delta_rotation;
292
293        let mut state_b = if index_b == NULL_INDEX {
294            IDENTITY_BODY_STATE
295        } else {
296            states[index_b as usize]
297        };
298        let mut v_b = state_b.linear_velocity;
299        let mut w_b = state_b.angular_velocity;
300        let dq_b = state_b.delta_rotation;
301
302        let dp = sub(state_b.delta_position, state_a.delta_position);
303
304        let normal = constraint.normal;
305        let tangent = right_perp(normal);
306        let friction = constraint.friction;
307        let softness = constraint.softness;
308
309        let point_count = constraint.point_count;
310        let mut total_normal_impulse = 0.0;
311
312        // Non-penetration
313        for j in 0..point_count as usize {
314            let cp = &mut constraint.points[j];
315
316            // fixed anchor points
317            let r_a = cp.anchor_a;
318            let r_b = cp.anchor_b;
319
320            // compute current separation
321            // this is subject to round-off error if the anchor is far from the
322            // body center of mass
323            let ds = add(dp, sub(rotate_vector(dq_b, r_b), rotate_vector(dq_a, r_a)));
324            let s = cp.base_separation + dot(ds, normal);
325
326            let mut velocity_bias = 0.0;
327            let mut mass_scale = 1.0;
328            let mut impulse_scale = 0.0;
329            if s > 0.0 {
330                // speculative bias
331                velocity_bias = s * inv_h;
332            } else if use_bias {
333                velocity_bias =
334                    max_float(softness.mass_scale * softness.bias_rate * s, -contact_speed);
335                mass_scale = softness.mass_scale;
336                impulse_scale = softness.impulse_scale;
337            }
338
339            // relative normal velocity at contact
340            let vr_a = add(v_a, cross_sv(w_a, r_a));
341            let vr_b = add(v_b, cross_sv(w_b, r_b));
342            let vn = dot(sub(vr_b, vr_a), normal);
343
344            // incremental normal impulse
345            let mut impulse = -cp.normal_mass * (mass_scale * vn + velocity_bias)
346                - impulse_scale * cp.normal_impulse;
347
348            // clamp the accumulated impulse
349            let new_impulse = max_float(cp.normal_impulse + impulse, 0.0);
350            impulse = new_impulse - cp.normal_impulse;
351            cp.normal_impulse = new_impulse;
352            cp.total_normal_impulse += impulse;
353
354            total_normal_impulse += new_impulse;
355
356            // apply normal impulse
357            let p = mul_sv(impulse, normal);
358            v_a = mul_sub(v_a, m_a, p);
359            w_a -= i_a * cross(r_a, p);
360
361            v_b = mul_add(v_b, m_b, p);
362            w_b += i_b * cross(r_b, p);
363        }
364
365        if !use_bias {
366            // Friction
367            for j in 0..point_count as usize {
368                let cp = &mut constraint.points[j];
369
370                // fixed anchor points
371                let r_a = cp.anchor_a;
372                let r_b = cp.anchor_b;
373
374                // relative tangent velocity at contact
375                let vr_b = add(v_b, cross_sv(w_b, r_b));
376                let vr_a = add(v_a, cross_sv(w_a, r_a));
377
378                // vt = dot(vrB - sB * tangent - (vrA + sA * tangent), tangent)
379                //    = dot(vrB - vrA, tangent) - (sA + sB)
380                let vt = dot(sub(vr_b, vr_a), tangent) - constraint.tangent_speed;
381
382                // incremental tangent impulse
383                let mut impulse = cp.tangent_mass * (-vt);
384
385                // clamp the accumulated force
386                let max_friction = friction * cp.normal_impulse;
387                let new_impulse =
388                    clamp_float(cp.tangent_impulse + impulse, -max_friction, max_friction);
389                impulse = new_impulse - cp.tangent_impulse;
390                cp.tangent_impulse = new_impulse;
391
392                // apply tangent impulse
393                let p = mul_sv(impulse, tangent);
394                v_a = mul_sub(v_a, m_a, p);
395                w_a -= i_a * cross(r_a, p);
396                v_b = mul_add(v_b, m_b, p);
397                w_b += i_b * cross(r_b, p);
398            }
399
400            // Rolling resistance
401            {
402                let mut delta_lambda = -constraint.rolling_mass * (w_b - w_a);
403                let lambda = constraint.rolling_impulse;
404                let max_lambda = constraint.rolling_resistance * total_normal_impulse;
405                constraint.rolling_impulse =
406                    clamp_float(lambda + delta_lambda, -max_lambda, max_lambda);
407                delta_lambda = constraint.rolling_impulse - lambda;
408
409                w_a -= i_a * delta_lambda;
410                w_b += i_b * delta_lambda;
411            }
412        }
413
414        if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
415            state_a.linear_velocity = v_a;
416            state_a.angular_velocity = w_a;
417            states[index_a as usize] = state_a;
418        }
419
420        if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
421            state_b.linear_velocity = v_b;
422            state_b.angular_velocity = w_b;
423            states[index_b as usize] = state_b;
424        }
425    }
426}
427
428/// (b2ApplyRestitution_Overflow / per-lane b2ApplyRestitutionTask)
429pub fn apply_restitution(
430    constraints: &mut [ContactConstraint],
431    states: &mut [BodyState],
432    context: &StepContext,
433) {
434    let threshold = context.restitution_threshold;
435
436    for constraint in constraints.iter_mut() {
437        let restitution = constraint.restitution;
438        if restitution == 0.0 {
439            continue;
440        }
441
442        let m_a = constraint.inv_mass_a;
443        let i_a = constraint.inv_i_a;
444        let m_b = constraint.inv_mass_b;
445        let i_b = constraint.inv_i_b;
446
447        let index_a = constraint.index_a - 1;
448        let index_b = constraint.index_b - 1;
449
450        // dummy state to represent a static body
451        let mut state_a = if index_a == NULL_INDEX {
452            IDENTITY_BODY_STATE
453        } else {
454            states[index_a as usize]
455        };
456        let mut v_a = state_a.linear_velocity;
457        let mut w_a = state_a.angular_velocity;
458
459        let mut state_b = if index_b == NULL_INDEX {
460            IDENTITY_BODY_STATE
461        } else {
462            states[index_b as usize]
463        };
464        let mut v_b = state_b.linear_velocity;
465        let mut w_b = state_b.angular_velocity;
466
467        let normal = constraint.normal;
468        let point_count = constraint.point_count;
469
470        // it is possible to get more accurate restitution by iterating
471        // this only makes a difference if there are two contact points
472        for j in 0..point_count as usize {
473            let cp = &mut constraint.points[j];
474
475            // if the normal impulse is zero then there was no collision
476            // this skips speculative contact points that didn't generate an
477            // impulse. The max normal impulse is used in case there was a
478            // collision that moved away within the sub-step process
479            if cp.relative_velocity > -threshold || cp.total_normal_impulse == 0.0 {
480                continue;
481            }
482
483            // fixed anchor points
484            let r_a = cp.anchor_a;
485            let r_b = cp.anchor_b;
486
487            // relative normal velocity at contact
488            let vr_b = add(v_b, cross_sv(w_b, r_b));
489            let vr_a = add(v_a, cross_sv(w_a, r_a));
490            let vn = dot(sub(vr_b, vr_a), normal);
491
492            // compute normal impulse
493            let mut impulse = -cp.normal_mass * (vn + restitution * cp.relative_velocity);
494
495            // clamp the accumulated impulse
496            let new_impulse = max_float(cp.normal_impulse + impulse, 0.0);
497            impulse = new_impulse - cp.normal_impulse;
498            cp.normal_impulse = new_impulse;
499            cp.total_normal_impulse += impulse;
500
501            // apply contact impulse
502            let p = mul_sv(impulse, normal);
503            v_a = mul_sub(v_a, m_a, p);
504            w_a -= i_a * cross(r_a, p);
505            v_b = mul_add(v_b, m_b, p);
506            w_b += i_b * cross(r_b, p);
507        }
508
509        if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
510            state_a.linear_velocity = v_a;
511            state_a.angular_velocity = w_a;
512            states[index_a as usize] = state_a;
513        }
514
515        if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
516            state_b.linear_velocity = v_b;
517            state_b.angular_velocity = w_b;
518            states[index_b as usize] = state_b;
519        }
520    }
521}
522
523/// (b2StoreImpulses_Overflow / per-lane b2StoreImpulsesTask)
524pub fn store_impulses(constraints: &[ContactConstraint], contacts: &mut [ContactSim]) {
525    for (constraint, contact) in constraints.iter().zip(contacts.iter_mut()) {
526        let manifold = &mut contact.manifold;
527        let point_count = manifold.point_count;
528
529        for j in 0..point_count as usize {
530            manifold.points[j].normal_impulse = constraint.points[j].normal_impulse;
531            manifold.points[j].tangent_impulse = constraint.points[j].tangent_impulse;
532            manifold.points[j].total_normal_impulse = constraint.points[j].total_normal_impulse;
533            manifold.points[j].normal_velocity = constraint.points[j].relative_velocity;
534        }
535
536        manifold.rolling_impulse = constraint.rolling_impulse;
537    }
538}