Skip to main content

box2d_rust/contact_solver/
wide_kernels.rs

1// Wide (SIMD) contact constraint kernels, ported from the b2*ContactsTask
2// functions in contact_solver.c. These run the graph-color contacts four lanes
3// at a time; the overflow color keeps the scalar kernels in
4// contact_solver.rs. Bodies within a graph color are disjoint, so gather /
5// scatter over lane indices is race-free and the per-lane arithmetic is
6// bit-identical to the scalar path (see wide.rs for the op-composition rules).
7//
8// A color's constraints are stored in a `Vec<ContactConstraintWide>` sized in
9// blocks of SIMD_WIDTH (ceil(count / 4)). The Vec is zero-initialized so the
10// dead tail lanes of the last block carry null indices (stored 0) and zero
11// data; gather returns identity for them and scatter skips them, so they have
12// no effect. This mirrors the C, which zeroes remainder lanes in solver setup
13// and never writes them in b2PrepareContactsTask.
14//
15// SPDX-FileCopyrightText: 2023 Erin Catto
16// SPDX-License-Identifier: MIT
17//
18// bring-up: called by the solver slice for graph colors.
19//
20// The prepare/store lane writes index a block by (i / 4, i % 4); the range
21// loops mirror the C's lane fill and are clearer than an iterator here.
22#![allow(clippy::needless_range_loop)]
23
24use super::wide::{
25    cross_w, dot_w, gather_bodies, rotate_vector_w, scatter_bodies, FloatW, Vec2W, SIMD_WIDTH,
26};
27use crate::body::BodyState;
28use crate::contact::ContactSim;
29use crate::core::NULL_INDEX;
30use crate::math_functions::max_float;
31use crate::math_functions::min_float;
32use crate::math_functions::{cross, dot, right_perp, sub, Vec2};
33use crate::solver::{make_soft, StepContext};
34
35/// Struct-of-lane-arrays contact constraint for four contacts. (b2ContactConstraintWide)
36#[derive(Debug, Clone, Copy, PartialEq, Default)]
37pub struct ContactConstraintWide {
38    /// base-1 body sim indices, 0 for null
39    pub index_a: [i32; SIMD_WIDTH],
40    pub index_b: [i32; SIMD_WIDTH],
41
42    pub inv_mass_a: FloatW,
43    pub inv_mass_b: FloatW,
44    pub inv_i_a: FloatW,
45    pub inv_i_b: FloatW,
46    pub normal: Vec2W,
47    pub friction: FloatW,
48    pub tangent_speed: FloatW,
49    pub rolling_resistance: FloatW,
50    pub rolling_mass: FloatW,
51    pub rolling_impulse: FloatW,
52    pub bias_rate: FloatW,
53    pub mass_scale: FloatW,
54    pub impulse_scale: FloatW,
55    pub anchor_a1: Vec2W,
56    pub anchor_b1: Vec2W,
57    pub normal_mass1: FloatW,
58    pub tangent_mass1: FloatW,
59    pub base_separation1: FloatW,
60    pub normal_impulse1: FloatW,
61    pub total_normal_impulse1: FloatW,
62    pub tangent_impulse1: FloatW,
63    pub anchor_a2: Vec2W,
64    pub anchor_b2: Vec2W,
65    pub base_separation2: FloatW,
66    pub normal_impulse2: FloatW,
67    pub total_normal_impulse2: FloatW,
68    pub tangent_impulse2: FloatW,
69    pub normal_mass2: FloatW,
70    pub tangent_mass2: FloatW,
71    pub restitution: FloatW,
72    pub relative_velocity1: FloatW,
73    pub relative_velocity2: FloatW,
74}
75
76/// Number of wide blocks needed for `contact_count` contacts.
77#[inline]
78pub fn wide_block_count(contact_count: usize) -> usize {
79    contact_count.div_ceil(SIMD_WIDTH)
80}
81
82/// Build the wide constraints for one color's touching contacts. `wide` is
83/// sized `wide_block_count(contacts.len())` and zero-initialized; only live
84/// lanes are written. (b2PrepareContactsTask, per color)
85#[allow(clippy::too_many_arguments)]
86pub fn prepare_contacts_wide(
87    wide: &mut [ContactConstraintWide],
88    contacts: &[ContactSim],
89    states: &[BodyState],
90    context: &StepContext,
91    enable_softening: bool,
92    contact_hertz: f32,
93    contact_damping_ratio: f32,
94) {
95    debug_assert!(wide.len() == wide_block_count(contacts.len()));
96
97    // Stiffer for static contacts to avoid bodies getting pushed through the
98    // ground
99    let contact_softness = context.contact_softness;
100    let static_softness = context.static_softness;
101
102    let warm_start_scale = if context.enable_warm_starting {
103        1.0
104    } else {
105        0.0
106    };
107
108    for (i, contact_sim) in contacts.iter().enumerate() {
109        let block = i / SIMD_WIDTH;
110        let lane = i % SIMD_WIDTH;
111        let constraint = &mut wide[block];
112
113        let manifold = &contact_sim.manifold;
114        let point_count = manifold.point_count;
115        debug_assert!(0 < point_count && point_count <= 2);
116
117        let index_a = contact_sim.body_sim_index_a;
118        let index_b = contact_sim.body_sim_index_b;
119
120        // 0 for null
121        constraint.index_a[lane] = index_a + 1;
122        constraint.index_b[lane] = index_b + 1;
123
124        let mut v_a = Vec2 { x: 0.0, y: 0.0 };
125        let mut w_a = 0.0;
126        let m_a = contact_sim.inv_mass_a;
127        let i_a = contact_sim.inv_i_a;
128        if index_a != NULL_INDEX {
129            let state_a = &states[index_a as usize];
130            v_a = state_a.linear_velocity;
131            w_a = state_a.angular_velocity;
132        }
133
134        let mut v_b = Vec2 { x: 0.0, y: 0.0 };
135        let mut w_b = 0.0;
136        let m_b = contact_sim.inv_mass_b;
137        let i_b = contact_sim.inv_i_b;
138        if index_b != NULL_INDEX {
139            let state_b = &states[index_b as usize];
140            v_b = state_b.linear_velocity;
141            w_b = state_b.angular_velocity;
142        }
143
144        constraint.inv_mass_a.0[lane] = m_a;
145        constraint.inv_mass_b.0[lane] = m_b;
146        constraint.inv_i_a.0[lane] = i_a;
147        constraint.inv_i_b.0[lane] = i_b;
148
149        {
150            let k = i_a + i_b;
151            constraint.rolling_mass.0[lane] = if k > 0.0 { 1.0 / k } else { 0.0 };
152        }
153
154        // Soft contact behavior. The overflow scalar path uses static/contact
155        // softness only; the graph-color wide path additionally supports the
156        // experimental per-contact softening feature, matching the C.
157        let soft = if index_a == NULL_INDEX || index_b == NULL_INDEX {
158            static_softness
159        } else if enable_softening {
160            let contact_hertz = min_float(contact_hertz, 0.125 * context.inv_h);
161            let ratio = if m_a < m_b {
162                max_float(0.5, m_a / m_b)
163            } else if m_b < m_a {
164                max_float(0.5, m_b / m_a)
165            } else {
166                1.0
167            };
168            make_soft(
169                ratio * contact_hertz,
170                ratio * contact_damping_ratio,
171                context.h,
172            )
173        } else {
174            contact_softness
175        };
176
177        let normal = manifold.normal;
178        constraint.normal.x.0[lane] = normal.x;
179        constraint.normal.y.0[lane] = normal.y;
180
181        constraint.friction.0[lane] = contact_sim.friction;
182        constraint.tangent_speed.0[lane] = contact_sim.tangent_speed;
183        constraint.restitution.0[lane] = contact_sim.restitution;
184        constraint.rolling_resistance.0[lane] = contact_sim.rolling_resistance;
185        constraint.rolling_impulse.0[lane] = warm_start_scale * manifold.rolling_impulse;
186
187        constraint.bias_rate.0[lane] = soft.bias_rate;
188        constraint.mass_scale.0[lane] = soft.mass_scale;
189        constraint.impulse_scale.0[lane] = soft.impulse_scale;
190
191        let tangent = right_perp(normal);
192
193        // point 1
194        {
195            let mp = &manifold.points[0];
196            let r_a = mp.anchor_a;
197            let r_b = mp.anchor_b;
198
199            constraint.anchor_a1.x.0[lane] = r_a.x;
200            constraint.anchor_a1.y.0[lane] = r_a.y;
201            constraint.anchor_b1.x.0[lane] = r_b.x;
202            constraint.anchor_b1.y.0[lane] = r_b.y;
203
204            constraint.base_separation1.0[lane] = mp.separation - dot(sub(r_b, r_a), normal);
205
206            constraint.normal_impulse1.0[lane] = warm_start_scale * mp.normal_impulse;
207            constraint.tangent_impulse1.0[lane] = warm_start_scale * mp.tangent_impulse;
208            constraint.total_normal_impulse1.0[lane] = 0.0;
209
210            let rn_a = cross(r_a, normal);
211            let rn_b = cross(r_b, normal);
212            let k_normal = m_a + m_b + i_a * rn_a * rn_a + i_b * rn_b * rn_b;
213            constraint.normal_mass1.0[lane] = if k_normal > 0.0 { 1.0 / k_normal } else { 0.0 };
214
215            let rt_a = cross(r_a, tangent);
216            let rt_b = cross(r_b, tangent);
217            let k_tangent = m_a + m_b + i_a * rt_a * rt_a + i_b * rt_b * rt_b;
218            constraint.tangent_mass1.0[lane] = if k_tangent > 0.0 {
219                1.0 / k_tangent
220            } else {
221                0.0
222            };
223
224            let vr_a = crate::math_functions::add(v_a, crate::math_functions::cross_sv(w_a, r_a));
225            let vr_b = crate::math_functions::add(v_b, crate::math_functions::cross_sv(w_b, r_b));
226            constraint.relative_velocity1.0[lane] = dot(normal, sub(vr_b, vr_a));
227        }
228
229        if point_count == 2 {
230            let mp = &manifold.points[1];
231            let r_a = mp.anchor_a;
232            let r_b = mp.anchor_b;
233
234            constraint.anchor_a2.x.0[lane] = r_a.x;
235            constraint.anchor_a2.y.0[lane] = r_a.y;
236            constraint.anchor_b2.x.0[lane] = r_b.x;
237            constraint.anchor_b2.y.0[lane] = r_b.y;
238
239            constraint.base_separation2.0[lane] = mp.separation - dot(sub(r_b, r_a), normal);
240
241            constraint.normal_impulse2.0[lane] = warm_start_scale * mp.normal_impulse;
242            constraint.tangent_impulse2.0[lane] = warm_start_scale * mp.tangent_impulse;
243            constraint.total_normal_impulse2.0[lane] = 0.0;
244
245            let rn_a = cross(r_a, normal);
246            let rn_b = cross(r_b, normal);
247            let k_normal = m_a + m_b + i_a * rn_a * rn_a + i_b * rn_b * rn_b;
248            constraint.normal_mass2.0[lane] = if k_normal > 0.0 { 1.0 / k_normal } else { 0.0 };
249
250            let rt_a = cross(r_a, tangent);
251            let rt_b = cross(r_b, tangent);
252            let k_tangent = m_a + m_b + i_a * rt_a * rt_a + i_b * rt_b * rt_b;
253            constraint.tangent_mass2.0[lane] = if k_tangent > 0.0 {
254                1.0 / k_tangent
255            } else {
256                0.0
257            };
258
259            let vr_a = crate::math_functions::add(v_a, crate::math_functions::cross_sv(w_a, r_a));
260            let vr_b = crate::math_functions::add(v_b, crate::math_functions::cross_sv(w_b, r_b));
261            constraint.relative_velocity2.0[lane] = dot(normal, sub(vr_b, vr_a));
262        } else {
263            // dummy data that has no effect
264            constraint.base_separation2.0[lane] = 0.0;
265            constraint.normal_impulse2.0[lane] = 0.0;
266            constraint.tangent_impulse2.0[lane] = 0.0;
267            constraint.total_normal_impulse2.0[lane] = 0.0;
268            constraint.anchor_a2.x.0[lane] = 0.0;
269            constraint.anchor_a2.y.0[lane] = 0.0;
270            constraint.anchor_b2.x.0[lane] = 0.0;
271            constraint.anchor_b2.y.0[lane] = 0.0;
272            constraint.normal_mass2.0[lane] = 0.0;
273            constraint.tangent_mass2.0[lane] = 0.0;
274            constraint.relative_velocity2.0[lane] = 0.0;
275        }
276    }
277}
278
279/// (b2WarmStartContactsTask, per color)
280pub fn warm_start_contacts_wide(wide: &mut [ContactConstraintWide], states: &mut [BodyState]) {
281    for c in wide.iter_mut() {
282        let mut b_a = gather_bodies(states, &c.index_a);
283        let mut b_b = gather_bodies(states, &c.index_b);
284
285        let tangent_x = c.normal.y;
286        let tangent_y = FloatW::zero().sub(c.normal.x);
287
288        // point 1
289        {
290            let r_a = c.anchor_a1;
291            let r_b = c.anchor_b1;
292
293            let p = Vec2W {
294                x: c.normal_impulse1
295                    .mul(c.normal.x)
296                    .add(c.tangent_impulse1.mul(tangent_x)),
297                y: c.normal_impulse1
298                    .mul(c.normal.y)
299                    .add(c.tangent_impulse1.mul(tangent_y)),
300            };
301            b_a.w = b_a.w.mul_sub(c.inv_i_a, cross_w(r_a, p));
302            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, p.x);
303            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, p.y);
304            b_b.w = b_b.w.mul_add(c.inv_i_b, cross_w(r_b, p));
305            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, p.x);
306            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, p.y);
307
308            c.total_normal_impulse1 = c.total_normal_impulse1.add(c.normal_impulse1);
309        }
310
311        // point 2
312        {
313            let r_a = c.anchor_a2;
314            let r_b = c.anchor_b2;
315
316            let p = Vec2W {
317                x: c.normal_impulse2
318                    .mul(c.normal.x)
319                    .add(c.tangent_impulse2.mul(tangent_x)),
320                y: c.normal_impulse2
321                    .mul(c.normal.y)
322                    .add(c.tangent_impulse2.mul(tangent_y)),
323            };
324            b_a.w = b_a.w.mul_sub(c.inv_i_a, cross_w(r_a, p));
325            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, p.x);
326            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, p.y);
327            b_b.w = b_b.w.mul_add(c.inv_i_b, cross_w(r_b, p));
328            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, p.x);
329            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, p.y);
330
331            c.total_normal_impulse2 = c.total_normal_impulse2.add(c.normal_impulse2);
332        }
333
334        b_a.w = b_a.w.mul_sub(c.inv_i_a, c.rolling_impulse);
335        b_b.w = b_b.w.mul_add(c.inv_i_b, c.rolling_impulse);
336
337        scatter_bodies(states, &c.index_a, &b_a);
338        scatter_bodies(states, &c.index_b, &b_b);
339    }
340}
341
342/// (b2SolveContactsTask, per color)
343pub fn solve_contacts_wide(
344    wide: &mut [ContactConstraintWide],
345    states: &mut [BodyState],
346    context: &StepContext,
347    use_bias: bool,
348) {
349    let inv_h = FloatW::splat(context.inv_h);
350    let contact_speed = FloatW::splat(-context.contact_speed);
351    let one_w = FloatW::splat(1.0);
352    let zero = FloatW::zero();
353
354    for c in wide.iter_mut() {
355        let mut b_a = gather_bodies(states, &c.index_a);
356        let mut b_b = gather_bodies(states, &c.index_b);
357
358        let (bias_rate, mass_scale, impulse_scale) = if use_bias {
359            (c.mass_scale.mul(c.bias_rate), c.mass_scale, c.impulse_scale)
360        } else {
361            (zero, one_w, zero)
362        };
363
364        let mut total_normal_impulse = zero;
365
366        let dp = Vec2W {
367            x: b_b.dp.x.sub(b_a.dp.x),
368            y: b_b.dp.y.sub(b_a.dp.y),
369        };
370
371        // point 1 non-penetration constraint
372        {
373            let r_a = c.anchor_a1;
374            let r_b = c.anchor_b1;
375
376            let rs_a = rotate_vector_w(b_a.dq, r_a);
377            let rs_b = rotate_vector_w(b_b.dq, r_b);
378
379            let ds = Vec2W {
380                x: dp.x.add(rs_b.x.sub(rs_a.x)),
381                y: dp.y.add(rs_b.y.sub(rs_a.y)),
382            };
383            let s = dot_w(c.normal, ds).add(c.base_separation1);
384
385            let mask = s.greater_than(zero);
386            let spec_bias = s.mul(inv_h);
387            let soft_bias = bias_rate.mul(s).max(contact_speed);
388            let bias = FloatW::blend(soft_bias, spec_bias, mask);
389
390            let point_mass_scale = FloatW::blend(mass_scale, one_w, mask);
391            let point_impulse_scale = FloatW::blend(impulse_scale, zero, mask);
392
393            let dvx = b_b
394                .v
395                .x
396                .sub(b_b.w.mul(r_b.y))
397                .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
398            let dvy = b_b
399                .v
400                .y
401                .add(b_b.w.mul(r_b.x))
402                .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
403            let vn = dvx.mul(c.normal.x).add(dvy.mul(c.normal.y));
404
405            let neg_impulse = c
406                .normal_mass1
407                .mul(point_mass_scale.mul(vn).add(bias))
408                .add(point_impulse_scale.mul(c.normal_impulse1));
409
410            let new_impulse = c.normal_impulse1.sub(neg_impulse).max(zero);
411            let impulse = new_impulse.sub(c.normal_impulse1);
412            c.normal_impulse1 = new_impulse;
413            c.total_normal_impulse1 = c.total_normal_impulse1.add(impulse);
414
415            total_normal_impulse = total_normal_impulse.add(new_impulse);
416
417            let px = impulse.mul(c.normal.x);
418            let py = impulse.mul(c.normal.y);
419
420            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
421            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
422            b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
423
424            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
425            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
426            b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
427        }
428
429        // point 2 non-penetration constraint
430        {
431            let rs_a = rotate_vector_w(b_a.dq, c.anchor_a2);
432            let rs_b = rotate_vector_w(b_b.dq, c.anchor_b2);
433
434            let ds = Vec2W {
435                x: dp.x.add(rs_b.x.sub(rs_a.x)),
436                y: dp.y.add(rs_b.y.sub(rs_a.y)),
437            };
438            let s = dot_w(c.normal, ds).add(c.base_separation2);
439
440            let mask = s.greater_than(zero);
441            let spec_bias = s.mul(inv_h);
442            let soft_bias = bias_rate.mul(s).max(contact_speed);
443            let bias = FloatW::blend(soft_bias, spec_bias, mask);
444
445            let point_mass_scale = FloatW::blend(mass_scale, one_w, mask);
446            let point_impulse_scale = FloatW::blend(impulse_scale, zero, mask);
447
448            let r_a = c.anchor_a2;
449            let r_b = c.anchor_b2;
450
451            let dvx = b_b
452                .v
453                .x
454                .sub(b_b.w.mul(r_b.y))
455                .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
456            let dvy = b_b
457                .v
458                .y
459                .add(b_b.w.mul(r_b.x))
460                .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
461            let vn = dvx.mul(c.normal.x).add(dvy.mul(c.normal.y));
462
463            let neg_impulse = c
464                .normal_mass2
465                .mul(point_mass_scale.mul(vn).add(bias))
466                .add(point_impulse_scale.mul(c.normal_impulse2));
467
468            let new_impulse = c.normal_impulse2.sub(neg_impulse).max(zero);
469            let impulse = new_impulse.sub(c.normal_impulse2);
470            c.normal_impulse2 = new_impulse;
471            c.total_normal_impulse2 = c.total_normal_impulse2.add(impulse);
472
473            total_normal_impulse = total_normal_impulse.add(new_impulse);
474
475            let px = impulse.mul(c.normal.x);
476            let py = impulse.mul(c.normal.y);
477
478            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
479            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
480            b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
481
482            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
483            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
484            b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
485        }
486
487        if !use_bias {
488            // Rolling resistance
489            if !c.rolling_resistance.all_zero() {
490                let delta_lambda = c.rolling_mass.mul(b_a.w.sub(b_b.w));
491                let lambda = c.rolling_impulse;
492                let max_lambda = c.rolling_resistance.mul(total_normal_impulse);
493                c.rolling_impulse = lambda.add(delta_lambda).sym_clamp(max_lambda);
494                let delta_lambda = c.rolling_impulse.sub(lambda);
495
496                b_a.w = b_a.w.mul_sub(c.inv_i_a, delta_lambda);
497                b_b.w = b_b.w.mul_add(c.inv_i_b, delta_lambda);
498            }
499
500            let tangent_x = c.normal.y;
501            let tangent_y = zero.sub(c.normal.x);
502
503            // point 1 friction constraint
504            {
505                let r_a = c.anchor_a1;
506                let r_b = c.anchor_b1;
507
508                let dvx = b_b
509                    .v
510                    .x
511                    .sub(b_b.w.mul(r_b.y))
512                    .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
513                let dvy = b_b
514                    .v
515                    .y
516                    .add(b_b.w.mul(r_b.x))
517                    .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
518                let vt = dvx.mul(tangent_x).add(dvy.mul(tangent_y));
519                let vt = vt.sub(c.tangent_speed);
520
521                let neg_impulse = c.tangent_mass1.mul(vt);
522
523                let max_friction = c.friction.mul(c.normal_impulse1);
524                let new_impulse = c.tangent_impulse1.sub(neg_impulse);
525                let new_impulse = zero.sub(max_friction).max(new_impulse.min(max_friction));
526                let impulse = new_impulse.sub(c.tangent_impulse1);
527                c.tangent_impulse1 = new_impulse;
528
529                let px = impulse.mul(tangent_x);
530                let py = impulse.mul(tangent_y);
531
532                b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
533                b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
534                b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
535
536                b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
537                b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
538                b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
539            }
540
541            // point 2 friction constraint
542            {
543                let r_a = c.anchor_a2;
544                let r_b = c.anchor_b2;
545
546                let dvx = b_b
547                    .v
548                    .x
549                    .sub(b_b.w.mul(r_b.y))
550                    .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
551                let dvy = b_b
552                    .v
553                    .y
554                    .add(b_b.w.mul(r_b.x))
555                    .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
556                let vt = dvx.mul(tangent_x).add(dvy.mul(tangent_y));
557                let vt = vt.sub(c.tangent_speed);
558
559                let neg_impulse = c.tangent_mass2.mul(vt);
560
561                let max_friction = c.friction.mul(c.normal_impulse2);
562                let new_impulse = c.tangent_impulse2.sub(neg_impulse);
563                let new_impulse = zero.sub(max_friction).max(new_impulse.min(max_friction));
564                let impulse = new_impulse.sub(c.tangent_impulse2);
565                c.tangent_impulse2 = new_impulse;
566
567                let px = impulse.mul(tangent_x);
568                let py = impulse.mul(tangent_y);
569
570                b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
571                b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
572                b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
573
574                b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
575                b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
576                b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
577            }
578        }
579
580        scatter_bodies(states, &c.index_a, &b_a);
581        scatter_bodies(states, &c.index_b, &b_b);
582    }
583}
584
585/// (b2ApplyRestitutionTask, per color)
586pub fn apply_restitution_wide(
587    wide: &mut [ContactConstraintWide],
588    states: &mut [BodyState],
589    context: &StepContext,
590) {
591    let threshold = FloatW::splat(context.restitution_threshold);
592    let zero = FloatW::zero();
593
594    for c in wide.iter_mut() {
595        if c.restitution.all_zero() {
596            // No lanes have restitution. Common case.
597            continue;
598        }
599
600        // lanes with no restitution are masked out below
601        let restitution_mask = c.restitution.equals(zero);
602
603        let mut b_a = gather_bodies(states, &c.index_a);
604        let mut b_b = gather_bodies(states, &c.index_b);
605
606        // point 1 non-penetration constraint
607        {
608            let mask1 = c.relative_velocity1.add(threshold).greater_than(zero);
609            let mask2 = c.total_normal_impulse1.equals(zero);
610            let mask = mask1.or(mask2).or(restitution_mask);
611            let mass = FloatW::blend(c.normal_mass1, zero, mask);
612
613            let r_a = c.anchor_a1;
614            let r_b = c.anchor_b1;
615
616            let dvx = b_b
617                .v
618                .x
619                .sub(b_b.w.mul(r_b.y))
620                .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
621            let dvy = b_b
622                .v
623                .y
624                .add(b_b.w.mul(r_b.x))
625                .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
626            let vn = dvx.mul(c.normal.x).add(dvy.mul(c.normal.y));
627
628            let neg_impulse = mass.mul(vn.add(c.restitution.mul(c.relative_velocity1)));
629
630            let new_impulse = c.normal_impulse1.sub(neg_impulse).max(zero);
631            let delta_impulse = new_impulse.sub(c.normal_impulse1);
632            c.normal_impulse1 = new_impulse;
633            c.total_normal_impulse1 = c.total_normal_impulse1.add(delta_impulse);
634
635            let px = delta_impulse.mul(c.normal.x);
636            let py = delta_impulse.mul(c.normal.y);
637
638            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
639            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
640            b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
641
642            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
643            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
644            b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
645        }
646
647        // point 2 non-penetration constraint
648        {
649            let mask1 = c.relative_velocity2.add(threshold).greater_than(zero);
650            let mask2 = c.total_normal_impulse2.equals(zero);
651            let mask = mask1.or(mask2).or(restitution_mask);
652            let mass = FloatW::blend(c.normal_mass2, zero, mask);
653
654            let r_a = c.anchor_a2;
655            let r_b = c.anchor_b2;
656
657            let dvx = b_b
658                .v
659                .x
660                .sub(b_b.w.mul(r_b.y))
661                .sub(b_a.v.x.sub(b_a.w.mul(r_a.y)));
662            let dvy = b_b
663                .v
664                .y
665                .add(b_b.w.mul(r_b.x))
666                .sub(b_a.v.y.add(b_a.w.mul(r_a.x)));
667            let vn = dvx.mul(c.normal.x).add(dvy.mul(c.normal.y));
668
669            let neg_impulse = mass.mul(vn.add(c.restitution.mul(c.relative_velocity2)));
670
671            let new_impulse = c.normal_impulse2.sub(neg_impulse).max(zero);
672            let delta_impulse = new_impulse.sub(c.normal_impulse2);
673            c.normal_impulse2 = new_impulse;
674            c.total_normal_impulse2 = c.total_normal_impulse2.add(delta_impulse);
675
676            let px = delta_impulse.mul(c.normal.x);
677            let py = delta_impulse.mul(c.normal.y);
678
679            b_a.v.x = b_a.v.x.mul_sub(c.inv_mass_a, px);
680            b_a.v.y = b_a.v.y.mul_sub(c.inv_mass_a, py);
681            b_a.w = b_a.w.mul_sub(c.inv_i_a, r_a.x.mul(py).sub(r_a.y.mul(px)));
682
683            b_b.v.x = b_b.v.x.mul_add(c.inv_mass_b, px);
684            b_b.v.y = b_b.v.y.mul_add(c.inv_mass_b, py);
685            b_b.w = b_b.w.mul_add(c.inv_i_b, r_b.x.mul(py).sub(r_b.y.mul(px)));
686        }
687
688        scatter_bodies(states, &c.index_a, &b_a);
689        scatter_bodies(states, &c.index_b, &b_b);
690    }
691}
692
693/// Write the accumulated impulses back to the color's contact manifolds. Hit
694/// events are flagged by the caller from the updated manifolds, matching the
695/// serial store path. (b2StoreImpulsesTask, write-back portion)
696pub fn store_impulses_wide(wide: &[ContactConstraintWide], contacts: &mut [ContactSim]) {
697    for (i, contact) in contacts.iter_mut().enumerate() {
698        let block = i / SIMD_WIDTH;
699        let lane = i % SIMD_WIDTH;
700        let c = &wide[block];
701
702        let manifold = &mut contact.manifold;
703        manifold.rolling_impulse = c.rolling_impulse.0[lane];
704
705        manifold.points[0].normal_impulse = c.normal_impulse1.0[lane];
706        manifold.points[0].tangent_impulse = c.tangent_impulse1.0[lane];
707        manifold.points[0].total_normal_impulse = c.total_normal_impulse1.0[lane];
708        manifold.points[0].normal_velocity = c.relative_velocity1.0[lane];
709
710        manifold.points[1].normal_impulse = c.normal_impulse2.0[lane];
711        manifold.points[1].tangent_impulse = c.tangent_impulse2.0[lane];
712        manifold.points[1].total_normal_impulse = c.total_normal_impulse2.0[lane];
713        manifold.points[1].normal_velocity = c.relative_velocity2.0[lane];
714    }
715}