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