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