Skip to main content

rapier2d/dynamics/solver/
solver_body.rs

1use crate::alloc_prelude::*;
2use crate::dynamics::RigidBody;
3use crate::math::{Real, SPATIAL_DIM, Vector};
4use crate::utils::{RotationOps, ScalarType};
5use core::ops::{AddAssign, Sub, SubAssign};
6use na::{DVectorView, DVectorViewMut};
7use parry::math::{Pose, Rotation, SIMD_WIDTH, SimdReal};
8
9use crate::utils::{SolverBlock, transpose_wide, transpose_wide_inv};
10
11macro_rules! aos(
12    ($data_repr: ident [ $idx: ident ] . $data_n: ident, $fallback: ident) => {
13        // One 128-bit block per lane (`SIMD_WIDTH` inferred from the consumer).
14        core::array::from_fn(|k| {
15            if ($idx[k] as usize) < $data_repr.len() {
16                $data_repr[$idx[k] as usize].$data_n.0
17            } else {
18                $fallback.$data_n.0
19            }
20        })
21    }
22);
23
24macro_rules! scatter(
25    ($data: ident [ $idx: ident [ $i: expr ] ] = [$($aos: ident),*]) => {
26       unsafe {
27            #[allow(clippy::missing_transmute_annotations)] // Different macro calls transmute to different types
28            if ($idx[$i] as usize) < $data.len() {
29                $data[$idx[$i] as usize] = core::mem::transmute([$($aos[$i]),*]);
30            }
31        }
32    }
33);
34
35/// Per-solver-body flag: bypass the angular speed cap. Kept in a
36/// `SolverBodies.flags` byte parallel to `vels`/`poses` so the scalar integrate-positions loop
37/// can read it without disturbing the SIMD-gathered `SolverVel`/`SolverPose` layouts.
38pub(crate) const SOLVER_BODY_ALLOW_FAST_ROTATION: u8 = 1;
39
40#[derive(Default)]
41pub struct SolverBodies {
42    pub vels: Vec<SolverVel<Real>>,
43    pub poses: Vec<SolverPose<Real>>,
44    /// Per-body flag bytes (see `SOLVER_BODY_ALLOW_FAST_ROTATION`), indexed like `vels`/`poses`.
45    pub flags: Vec<u8>,
46}
47
48impl SolverBodies {
49    pub fn clear(&mut self) {
50        self.vels.clear();
51        self.poses.clear();
52        self.flags.clear();
53    }
54
55    pub fn resize(&mut self, sz: usize) {
56        self.vels.resize(sz, Default::default());
57        self.poses.resize(sz, Default::default());
58        self.flags.resize(sz, 0);
59    }
60
61    pub fn len(&self) -> usize {
62        self.vels.len()
63    }
64
65    /// Panics if any non-world (`!= u32::MAX`) solver-body id is out of range. Part of the
66    /// `solver-bounds-checks` guards: a stale id (e.g. a contact-graph maintenance bug) would
67    /// silently gather the world-body fallback instead of the real body — this turns that into
68    /// a clean panic. Called once per chunk at constraint generation (NOT per solve iteration).
69    #[cfg(feature = "solver-bounds-checks")]
70    #[inline]
71    pub fn assert_ids_in_range(&self, idx: [u32; SIMD_WIDTH]) {
72        let len = self.len();
73        for id in idx {
74            assert!(
75                id == u32::MAX || (id as usize) < len,
76                "stale solver-body id {id} (solver bodies: {len}) — solver contact graph corruption"
77            );
78        }
79    }
80
81    // TODO: add a SIMD version?
82    pub fn copy_from(&mut self, i: usize, rb: &RigidBody) {
83        let poses = &mut self.poses[i];
84        let vels = &mut self.vels[i];
85
86        self.flags[i] = if rb.ccd.allow_fast_rotation {
87            SOLVER_BODY_ALLOW_FAST_ROTATION
88        } else {
89            0
90        };
91
92        // NOTE: gyroscopic forces (3D) are applied per-substep by the staged
93        // solver's velocity-increment stage, not here.
94        #[cfg(feature = "dim2")]
95        {
96            vels.angular = rb.vels.angvel;
97        }
98        #[cfg(feature = "dim3")]
99        {
100            vels.angular = rb.angvel();
101        }
102        vels.linear = rb.vels.linvel;
103
104        let pose = rb
105            .pos
106            .position
107            .prepend_translation(rb.mprops.local_mprops.local_com);
108        poses.rotation = pose.rotation;
109        poses.translation = pose.translation;
110
111        // A sleeping body only reaches the solver as a frontier solver body (partial-island
112        // sleep): a kinematic-like read-only wall at its real pose, so its mass properties
113        // must read as infinite despite its dynamic body type.
114        if rb.is_dynamic_or_kinematic() && !rb.is_sleeping() {
115            poses.ii = rb.mprops.effective_world_inv_inertia;
116            poses.im = rb.mprops.effective_inv_mass;
117        } else {
118            poses.ii = Default::default();
119            poses.im = Default::default();
120        }
121    }
122
123    #[inline]
124    pub fn gather_vels(&self, idx: [u32; SIMD_WIDTH]) -> SolverVel<SimdReal> {
125        SolverVel::gather(&self.vels, idx)
126    }
127
128    #[inline]
129    pub fn get_vel(&self, i: u32) -> SolverVel<Real> {
130        self.vels.get(i as usize).copied().unwrap_or_default()
131    }
132
133    #[inline]
134    pub fn scatter_vels(&mut self, idx: [u32; SIMD_WIDTH], vels: SolverVel<SimdReal>) {
135        vels.scatter(&mut self.vels, idx);
136    }
137
138    #[inline]
139    pub fn set_vel(&mut self, i: u32, vel: SolverVel<Real>) {
140        if (i as usize) < self.vels.len() {
141            self.vels[i as usize] = vel;
142        }
143    }
144
145    #[inline]
146    pub fn get_pose(&self, i: u32) -> SolverPose<Real> {
147        self.poses.get(i as usize).copied().unwrap_or_default()
148    }
149
150    #[inline]
151    pub fn gather_poses(&self, idx: [u32; SIMD_WIDTH]) -> SolverPose<SimdReal> {
152        SolverPose::gather(&self.poses, idx)
153    }
154
155    /// Gathers only the transform part (rotation + translation) of the solver poses — half (2D)
156    /// or less (3D) of [`Self::gather_poses`]'s transposition work; for kernels that keep the
157    /// (step-constant) mass properties in their own constraint storage.
158    #[inline]
159    pub fn gather_transforms(&self, idx: [u32; SIMD_WIDTH]) -> SolverTransform<SimdReal> {
160        SolverTransform::gather(&self.poses, idx)
161    }
162}
163
164// Total 7/13
165#[repr(C)]
166#[repr(align(16))]
167#[derive(Copy, Clone, Default)]
168pub struct SolverVel<T: ScalarType> {
169    pub linear: T::Vector,     // 2/3
170    pub angular: T::AngVector, // 1/3
171    // TODO: explicit padding are useful for static assertions.
172    //       But might be wasteful for the SolverVel<SimdReal>
173    //       specialization.
174    #[cfg(feature = "dim2")]
175    padding: [T; 1],
176    #[cfg(feature = "dim3")]
177    padding: [T; 2],
178}
179
180#[repr(C)]
181struct SolverVelRepr {
182    data0: SolverBlock,
183    #[cfg(feature = "dim3")]
184    data1: SolverBlock,
185}
186
187impl SolverVelRepr {
188    pub fn zero() -> Self {
189        Self {
190            data0: na::zero(),
191            #[cfg(feature = "dim3")]
192            data1: na::zero(),
193        }
194    }
195}
196
197impl SolverVel<SimdReal> {
198    #[inline]
199    pub fn gather(data: &[SolverVel<Real>], idx: [u32; SIMD_WIDTH]) -> Self {
200        // TODO: double-check that the compiler is using simd loads and
201        //       isn’t generating useless copies.
202
203        let zero = SolverVelRepr::zero();
204        let data_repr: &[SolverVelRepr] = unsafe { core::mem::transmute(data) };
205
206        #[cfg(feature = "dim2")]
207        {
208            let aos = aos!(data_repr[idx].data0, zero);
209            let soa = transpose_wide(aos);
210            unsafe { core::mem::transmute(soa) }
211        }
212
213        #[cfg(feature = "dim3")]
214        {
215            let aos0 = aos!(data_repr[idx].data0, zero);
216            let soa0 = transpose_wide(aos0);
217            let aos1 = aos!(data_repr[idx].data1, zero);
218            let soa1 = transpose_wide(aos1);
219            unsafe { core::mem::transmute((soa0, soa1)) }
220        }
221    }
222
223    #[inline]
224    #[cfg(feature = "dim2")]
225    pub fn scatter(self, data: &mut [SolverVel<Real>], idx: [u32; SIMD_WIDTH]) {
226        // TODO: double-check that the compiler is using simd loads and no useless copies.
227        let soa: [SimdReal; 4] = unsafe { core::mem::transmute(self) };
228        let aos = transpose_wide_inv(soa);
229        for i in 0..SIMD_WIDTH {
230            scatter!(data[idx[i]] = [aos]);
231        }
232    }
233
234    #[inline]
235    #[cfg(feature = "dim3")]
236    pub fn scatter(self, data: &mut [SolverVel<Real>], idx: [u32; SIMD_WIDTH]) {
237        let soa: [[SimdReal; 4]; 2] = unsafe { core::mem::transmute(self) };
238        // TODO: double-check that the compiler is using simd loads and no useless copies.
239        let aos0 = transpose_wide_inv(soa[0]);
240        let aos1 = transpose_wide_inv(soa[1]);
241        for i in 0..SIMD_WIDTH {
242            scatter!(data[idx[i]] = [aos0, aos1]);
243        }
244    }
245}
246
247// Total: 7/16
248#[repr(C)]
249#[repr(align(16))]
250#[derive(Copy, Clone)]
251pub struct SolverPose<N: ScalarType> {
252    /// Positional change of the rigid-body’s center of mass.
253    pub rotation: N::Rotation, // 2/4
254    pub translation: N::Vector, // 2/3
255    pub ii: N::AngInertia,      // 1/6
256    pub im: N::Vector,          // 2/3
257    #[cfg(feature = "dim2")]
258    pub padding: [N; 1],
259}
260
261impl SolverPose<Real> {
262    pub fn pose(&self) -> Pose {
263        Pose::from_parts(self.translation, self.rotation)
264    }
265}
266
267impl SolverPose<SimdReal> {
268    pub fn pose(&self) -> <SimdReal as ScalarType>::Pose {
269        <SimdReal as ScalarType>::Pose::from_parts(self.translation.into(), self.rotation)
270    }
271}
272
273impl<N: ScalarType> SolverPose<N> {
274    #[inline]
275    pub fn transform_point(&self, pt: N::Vector) -> N::Vector {
276        self.rotation * pt + self.translation
277    }
278
279    #[inline]
280    pub fn inverse_transform_point(&self, pt: N::Vector) -> N::Vector {
281        self.rotation.inverse() * (pt - self.translation)
282    }
283}
284
285/// The transform part of a [`SolverPose`], gathered without its mass properties. The field
286/// order deliberately matches the head of [`SolverPose`] so the SIMD gather can transpose only
287/// the leading pose blocks.
288#[repr(C)]
289#[derive(Copy, Clone)]
290pub struct SolverTransform<N: ScalarType> {
291    pub rotation: N::Rotation,
292    pub translation: N::Vector,
293}
294
295impl<N: ScalarType> SolverTransform<N> {
296    #[inline]
297    pub fn transform_point(&self, pt: N::Vector) -> N::Vector {
298        self.rotation * pt + self.translation
299    }
300}
301
302impl SolverTransform<SimdReal> {
303    #[inline]
304    pub fn gather(data: &[SolverPose<Real>], idx: [u32; SIMD_WIDTH]) -> Self {
305        let identity = SolverPoseRepr::identity();
306        let data_repr: &[SolverPoseRepr] = unsafe { core::mem::transmute(data) };
307
308        #[cfg(feature = "dim2")]
309        {
310            // In 2D the (rotation, translation) pair is exactly the first SIMD
311            // block of the pose: one transposition instead of two.
312            let aos0 = aos!(data_repr[idx].data0, identity);
313            let soa0 = transpose_wide(aos0);
314            unsafe { core::mem::transmute(soa0) }
315        }
316
317        #[cfg(feature = "dim3")]
318        {
319            // In 3D the quaternion + translation live in the first two SIMD
320            // blocks of the pose: two transpositions instead of four.
321            let aos0 = aos!(data_repr[idx].data0, identity);
322            let aos1 = aos!(data_repr[idx].data1, identity);
323            let soa0 = transpose_wide(aos0);
324            let soa1 = transpose_wide(aos1);
325
326            #[repr(C)]
327            struct TransformAndPad {
328                transform: SolverTransform<SimdReal>,
329                // First inertia element sharing the second pose block.
330                _ii_xx: SimdReal,
331            }
332            let repr: TransformAndPad = unsafe { core::mem::transmute([soa0, soa1]) };
333            repr.transform
334        }
335    }
336}
337
338impl Default for SolverPose<Real> {
339    #[inline]
340    fn default() -> Self {
341        Self {
342            rotation: Rotation::IDENTITY,
343            translation: Vector::ZERO,
344            ii: Default::default(),
345            im: Default::default(),
346            #[cfg(feature = "dim2")]
347            padding: Default::default(),
348        }
349    }
350}
351
352#[repr(C)]
353struct SolverPoseRepr {
354    data0: SolverBlock,
355    data1: SolverBlock,
356    #[cfg(feature = "dim3")]
357    data2: SolverBlock,
358    #[cfg(feature = "dim3")]
359    data3: SolverBlock,
360}
361
362impl SolverPoseRepr {
363    pub fn identity() -> Self {
364        // TODO PERF: will the compiler handle this efficiently and generate
365        //            everything at compile-time?
366        unsafe { core::mem::transmute(SolverPose::default()) }
367    }
368}
369
370impl SolverPose<SimdReal> {
371    #[inline]
372    pub fn gather(data: &[SolverPose<Real>], idx: [u32; SIMD_WIDTH]) -> Self {
373        // TODO: double-check that the compiler is using simd loads and
374        //       isn’t generating useless copies.
375
376        let identity = SolverPoseRepr::identity();
377        let data_repr: &[SolverPoseRepr] = unsafe { core::mem::transmute(data) };
378
379        #[cfg(feature = "dim2")]
380        {
381            let aos0 = aos!(data_repr[idx].data0, identity);
382            let aos1 = aos!(data_repr[idx].data1, identity);
383            let soa0 = transpose_wide(aos0);
384            let soa1 = transpose_wide(aos1);
385            unsafe { core::mem::transmute([soa0, soa1]) }
386        }
387
388        #[cfg(feature = "dim3")]
389        {
390            let aos0 = aos!(data_repr[idx].data0, identity);
391            let aos1 = aos!(data_repr[idx].data1, identity);
392            let aos2 = aos!(data_repr[idx].data2, identity);
393            let aos3 = aos!(data_repr[idx].data3, identity);
394            let soa0 = transpose_wide(aos0);
395            let soa1 = transpose_wide(aos1);
396            let soa2 = transpose_wide(aos2);
397            let soa3 = transpose_wide(aos3);
398            unsafe { core::mem::transmute([soa0, soa1, soa2, soa3]) }
399        }
400    }
401}
402
403impl<N: ScalarType> SolverVel<N> {
404    pub fn as_slice(&self) -> &[N; SPATIAL_DIM] {
405        unsafe { core::mem::transmute(self) }
406    }
407
408    pub fn as_mut_slice(&mut self) -> &mut [N; SPATIAL_DIM] {
409        unsafe { core::mem::transmute(self) }
410    }
411
412    pub fn as_vector_slice(&self) -> DVectorView<'_, N> {
413        DVectorView::from_slice(&self.as_slice()[..], SPATIAL_DIM)
414    }
415
416    pub fn as_vector_slice_mut(&mut self) -> DVectorViewMut<'_, N> {
417        DVectorViewMut::from_slice(&mut self.as_mut_slice()[..], SPATIAL_DIM)
418    }
419}
420
421impl<N: ScalarType> SolverVel<N> {
422    pub fn zero() -> Self {
423        Self {
424            linear: Default::default(),
425            angular: Default::default(),
426            #[cfg(feature = "dim2")]
427            padding: [na::zero(); 1],
428            #[cfg(feature = "dim3")]
429            padding: [na::zero(); 2],
430        }
431    }
432}
433
434impl<N: ScalarType> AddAssign for SolverVel<N> {
435    fn add_assign(&mut self, rhs: Self) {
436        self.linear += rhs.linear;
437        self.angular += rhs.angular;
438    }
439}
440
441impl<N: ScalarType> SubAssign for SolverVel<N> {
442    fn sub_assign(&mut self, rhs: Self) {
443        self.linear -= rhs.linear;
444        self.angular -= rhs.angular;
445    }
446}
447
448impl<N: ScalarType> Sub for SolverVel<N> {
449    type Output = Self;
450
451    fn sub(self, rhs: Self) -> Self {
452        SolverVel {
453            linear: self.linear - rhs.linear,
454            angular: self.angular - rhs.angular,
455            padding: self.padding,
456        }
457    }
458}