Skip to main content

rapier2d/utils/
mod.rs

1//! Miscellaneous utilities.
2
3#[cfg(not(target_arch = "spirv"))]
4mod angular_inertia_ops;
5mod component_mul;
6mod copysign;
7mod cross_product;
8mod cross_product_matrix;
9mod dot_product;
10mod fp_flags;
11mod index_mut2;
12mod matrix_column;
13mod orthonormal_basis;
14#[cfg(not(target_arch = "spirv"))]
15mod pos_ops;
16#[cfg(all(feature = "alloc", not(target_arch = "spirv")))]
17mod prefetch;
18#[cfg(not(target_arch = "spirv"))]
19mod rotation_ops;
20#[cfg(not(target_arch = "spirv"))]
21mod scalar_type;
22mod simd_real_copy;
23mod simd_select;
24
25pub use component_mul::ComponentMul;
26pub use copysign::CopySign;
27pub use index_mut2::IndexMut2;
28pub use matrix_column::MatrixColumn;
29pub use orthonormal_basis::OrthonormalBasis;
30#[cfg(not(target_arch = "spirv"))]
31pub use pos_ops::PoseOps;
32#[cfg(all(feature = "alloc", not(target_arch = "spirv")))]
33pub(crate) use prefetch::prefetch_read;
34#[cfg(not(target_arch = "spirv"))]
35pub use rotation_ops::RotationOps;
36#[cfg(not(target_arch = "spirv"))]
37pub use scalar_type::ScalarType;
38pub use simd_real_copy::SimdRealCopy;
39pub use simd_select::SimdSelect;
40
41#[cfg(not(target_arch = "spirv"))]
42pub use angular_inertia_ops::AngularInertiaOps;
43pub use cross_product::CrossProduct;
44pub use cross_product_matrix::CrossProductMatrix;
45pub use dot_product::{DotProduct, SimdLength};
46#[allow(unused_imports)]
47pub(crate) use fp_flags::DisableFloatingPointExceptionsFlags;
48
49#[cfg(feature = "alloc")]
50use crate::math::SIMD_WIDTH;
51#[cfg(not(target_arch = "spirv"))]
52use crate::math::SimdVector;
53use crate::math::{Real, Vector};
54#[cfg(all(feature = "dim2", not(target_arch = "spirv")))]
55use na::Matrix2;
56#[cfg(all(feature = "dim3", not(target_arch = "spirv")))]
57use na::Matrix3;
58
59/// Dimension minus one (1 for 2D, 2 for 3D).
60#[cfg(feature = "dim2")]
61pub const DIM_MINUS_ONE: usize = 1;
62/// Dimension minus one (1 for 2D, 2 for 3D).
63#[cfg(feature = "dim3")]
64pub const DIM_MINUS_ONE: usize = 2;
65
66/// Try to normalize a vector and return both the normalized vector and the original length.
67///
68/// Returns `None` if the vector's length is below the threshold.
69/// This is the glam equivalent of nalgebra's `Unit::try_new_and_get`.
70pub fn try_normalize_and_get_length(v: Vector, threshold: Real) -> Option<(Vector, Real)> {
71    let len = v.length();
72    if len > threshold {
73        Some((v / len, len))
74    } else {
75        None
76    }
77}
78
79/// Forces a negative-zero float (or any negative-zero component) to positive zero via
80/// `x + 0.0`, leaving every other value untouched.
81///
82/// Serialized solver state must not carry `-0.0`: whether a min/max/clamp returns `+0.0`
83/// or `-0.0` for a signed-zero tie is platform-specific (SSE returns one of the operands
84/// picked by argument order, NEON's `fminnm`/`fmaxnm` order the zeros), so a stored
85/// signed zero breaks cross-platform snapshot determinism even though `-0.0 == +0.0`
86/// dynamically.
87// Unused in no-alloc builds: the contact solver, its only caller, needs alloc.
88#[allow(dead_code)]
89#[inline(always)]
90pub(crate) fn canonicalize_zero<T>(x: T) -> T
91where
92    T: core::ops::Add<Output = T> + Default,
93{
94    #[cfg(feature = "enhanced-determinism")]
95    {
96        x + T::default()
97    }
98    #[cfg(not(feature = "enhanced-determinism"))]
99    {
100        x
101    }
102}
103
104/// Convert glam Vector to nalgebra `SimdVector<Real>`
105#[cfg(not(target_arch = "spirv"))]
106#[inline]
107pub fn vect_to_na(v: Vector) -> SimdVector<Real> {
108    v.into()
109}
110
111#[cfg(not(target_arch = "spirv"))]
112use crate::math::Matrix;
113
114/// Convert glam Matrix to nalgebra `Matrix2<Real>` (2D matrix)
115#[cfg(all(feature = "dim2", not(target_arch = "spirv")))]
116#[inline]
117pub fn mat_to_na(m: Matrix) -> Matrix2<Real> {
118    m.into()
119}
120
121/// Convert glam Matrix to nalgebra `Matrix3<Real>` (3D matrix)
122#[cfg(all(feature = "dim3", not(target_arch = "spirv")))]
123#[inline]
124pub fn mat_to_na(m: Matrix) -> Matrix3<Real> {
125    Matrix3::new(
126        m.x_axis.x, m.y_axis.x, m.z_axis.x, m.x_axis.y, m.y_axis.y, m.z_axis.y, m.x_axis.z,
127        m.y_axis.z, m.z_axis.z,
128    )
129}
130
131const INV_EPSILON: Real = 1.0e-20;
132
133#[allow(dead_code)]
134pub(crate) fn inv(val: Real) -> Real {
135    if (-INV_EPSILON..=INV_EPSILON).contains(&val) {
136        0.0
137    } else {
138        1.0 / val
139    }
140}
141
142#[allow(dead_code)]
143pub(crate) fn simd_inv<N: SimdRealCopy>(val: N) -> N {
144    let eps = N::splat(INV_EPSILON);
145    N::zero().select(val.simd_gt(-eps) & val.simd_lt(eps), N::one() / val)
146}
147
148#[allow(dead_code)]
149pub(crate) fn select_other<T: PartialEq>(pair: (T, T), elt: T) -> T {
150    if pair.0 == elt { pair.1 } else { pair.0 }
151}
152
153/// `Sync`, unless the `unsync-callbacks` feature says otherwise.
154#[cfg(not(feature = "unsync-callbacks"))]
155pub trait MaybeSync: Sync {}
156#[cfg(not(feature = "unsync-callbacks"))]
157impl<T: Sync + ?Sized> MaybeSync for T {}
158
159/// See the non-`unsync-callbacks` variant of this trait.
160#[cfg(feature = "unsync-callbacks")]
161pub trait MaybeSync {}
162#[cfg(feature = "unsync-callbacks")]
163impl<T: ?Sized> MaybeSync for T {}
164
165/// Removes a key from a [`parry::utils::hashmap::HashMap`] without caring about the
166/// resulting entry order.
167///
168/// The map is an `IndexMap` under `enhanced-determinism` and a `hashbrown` map otherwise,
169/// and only the former has (and demands) the order-explicit `swap_remove`. Its order still
170/// only depends on the sequence of operations, so swapping stays deterministic.
171#[cfg(feature = "alloc")]
172pub(crate) fn hashmap_remove<K, V>(
173    map: &mut parry::utils::hashmap::HashMap<K, V>,
174    key: &K,
175) -> Option<V>
176where
177    K: core::hash::Hash + Eq,
178{
179    #[cfg(feature = "enhanced-determinism")]
180    return map.swap_remove(key);
181    #[cfg(not(feature = "enhanced-determinism"))]
182    return map.remove(key);
183}
184
185/// A raw pointer to an array of `T` that can be shared across threads.
186///
187/// Safety: this is only sound if each element is accessed by at most one
188/// thread at a time (threads own disjoint sets of indices).
189#[cfg(feature = "parallel")]
190#[derive(Copy, Clone)]
191pub(crate) struct SyncPtr<T>(pub *mut T);
192#[cfg(feature = "parallel")]
193unsafe impl<T: Send> Send for SyncPtr<T> {}
194#[cfg(feature = "parallel")]
195unsafe impl<T: Send> Sync for SyncPtr<T> {}
196
197#[cfg(feature = "parallel")]
198impl<T> SyncPtr<T> {
199    /// Pointer to the `i`-th element. Safety: mutating through it is only sound
200    /// under the struct-level contract (disjoint per-thread element indices).
201    pub(crate) fn add(&self, i: usize) -> *mut T {
202        unsafe { self.0.add(i) }
203    }
204}
205
206/// Calculate the difference with smallest absolute value between the two given values.
207pub fn smallest_abs_diff_between_sin_angles<N: SimdRealCopy>(a: N, b: N) -> N {
208    // Select the smallest path among the two angles to reach the target.
209    let s_err = a - b;
210    let sgn = s_err.simd_signum();
211    let s_err_complement = s_err - sgn * N::splat(2.0);
212    let s_err_is_smallest = s_err.simd_abs().simd_lt(s_err_complement.simd_abs());
213    s_err.select(s_err_is_smallest, s_err_complement)
214}
215
216/// Calculate the difference with smallest absolute value between the two given angles.
217pub fn smallest_abs_diff_between_angles<N: SimdRealCopy>(a: N, b: N) -> N {
218    // Select the smallest path among the two angles to reach the target.
219    let s_err = a - b;
220    let sgn = s_err.simd_signum();
221    let s_err_complement = s_err - sgn * N::simd_two_pi();
222    let s_err_is_smallest = s_err.simd_abs().simd_lt(s_err_complement.simd_abs());
223    s_err.select(s_err_is_smallest, s_err_complement)
224}
225
226/// A single solver body's 4-scalar storage block, used to reinterpret a scalar
227/// `SolverVel`/`SolverPose`/`SolverContact` as fixed 4-wide chunks for the
228/// AoS↔SoA gather/scatter transpose.
229///
230/// This is deliberately **always** 4 lanes, independent of [`SIMD_WIDTH`]: it
231/// describes one body's data layout, not the SIMD lane count. At f32 and the
232/// default 4-lane width it is exactly `SimdReal`; at 8 lanes `SimdReal` widens
233/// to 256-bit while a per-body block stays 128-bit.
234#[cfg(all(feature = "alloc", feature = "f32"))]
235pub(crate) type SolverBlock = simba::simd::WideF32x4;
236/// See [`SolverBlock`]. `wide::f64x4` is 32-byte aligned, which would over-align
237/// a block past the 16-byte AoS rows the scalar structs are laid out in, so the
238/// f64 build keeps the plain-array block.
239#[cfg(all(feature = "alloc", feature = "f64"))]
240pub(crate) type SolverBlock = simba::simd::AutoF64x4;
241
242/// One body's block as the plain array the `aos!` gather hands over — what
243/// [`SolverBlock`] wraps, and what the transpose below operates on.
244#[cfg(all(feature = "alloc", feature = "f32"))]
245pub(crate) type RawBlock = wide::f32x4;
246/// See [`RawBlock`].
247#[cfg(all(feature = "alloc", feature = "f64"))]
248pub(crate) type RawBlock = [Real; 4];
249
250/// A 4x4 block transpose. Pure data movement — no arithmetic — so both
251/// implementations below are bit-exact and interchangeable.
252#[cfg(all(feature = "alloc", feature = "f32"))]
253#[inline(always)]
254fn transpose4(data: [RawBlock; 4]) -> [RawBlock; 4] {
255    wide::f32x4::transpose(data)
256}
257
258/// See [`transpose4`].
259#[cfg(all(feature = "alloc", feature = "f64"))]
260#[inline(always)]
261fn transpose4(data: [RawBlock; 4]) -> [RawBlock; 4] {
262    let [
263        [a0, a1, a2, a3],
264        [b0, b1, b2, b3],
265        [c0, c1, c2, c3],
266        [d0, d1, d2, d3],
267    ] = data;
268    [
269        [a0, b0, c0, d0],
270        [a1, b1, c1, d1],
271        [a2, b2, c2, d2],
272        [a3, b3, c3, d3],
273    ]
274}
275
276/// Transposes `SIMD_WIDTH` bodies' blocks (AoS) into 4 SoA lane-vectors, one per
277/// float field. Inverse of [`transpose_wide_inv`].
278///
279/// At 4 lanes this is a single [`transpose4`]. At 8 lanes it does two 4x4
280/// transposes (bodies 0–3 / 4–7) and concatenates each field's two halves.
281#[cfg(feature = "alloc")]
282#[inline(always)]
283pub(crate) fn transpose_wide(aos: [RawBlock; SIMD_WIDTH]) -> [crate::math::SimdReal; 4] {
284    #[cfg(not(feature = "simd8"))]
285    {
286        unsafe { core::mem::transmute(transpose4(aos)) }
287    }
288    #[cfg(feature = "simd8")]
289    {
290        let lo = transpose4([aos[0], aos[1], aos[2], aos[3]]);
291        let hi = transpose4([aos[4], aos[5], aos[6], aos[7]]);
292        // Field j spans body 0..8: lanes 0..4 from the low half, 4..8 from the high.
293        core::array::from_fn(|j| unsafe {
294            core::mem::transmute::<[RawBlock; 2], crate::math::SimdReal>([lo[j], hi[j]])
295        })
296    }
297}
298
299/// Transposes 4 SoA lane-vectors back into `SIMD_WIDTH` bodies' blocks (AoS).
300/// Inverse of [`transpose_wide`].
301#[cfg(feature = "alloc")]
302#[inline(always)]
303pub(crate) fn transpose_wide_inv(soa: [crate::math::SimdReal; 4]) -> [RawBlock; SIMD_WIDTH] {
304    #[cfg(not(feature = "simd8"))]
305    {
306        transpose4(unsafe {
307            core::mem::transmute::<[crate::math::SimdReal; 4], [RawBlock; 4]>(soa)
308        })
309    }
310    #[cfg(feature = "simd8")]
311    {
312        // Split each 8-lane field into its low/high 4-lane halves.
313        let split: [[RawBlock; 2]; 4] = unsafe { core::mem::transmute(soa) };
314        let lo: [RawBlock; 4] = core::array::from_fn(|j| split[j][0]);
315        let hi: [RawBlock; 4] = core::array::from_fn(|j| split[j][1]);
316        let aos_lo = transpose4(lo); // bodies 0..4
317        let aos_hi = transpose4(hi); // bodies 4..8
318        core::array::from_fn(|i| if i < 4 { aos_lo[i] } else { aos_hi[i - 4] })
319    }
320}
321
322/// Helpers around serialization.
323#[cfg(feature = "serde-serialize")]
324pub mod serde {
325    use crate::alloc_prelude::*;
326    use core::iter::FromIterator;
327    use serde::{Deserialize, Serialize};
328
329    /// Serializes to a `Vec<(K, V)>`.
330    ///
331    /// Useful for [`std::collections::HashMap`] with a non-string key,
332    /// which is unsupported by [`serde_json`](https://docs.rs/serde_json/).
333    pub fn serialize_to_vec_tuple<
334        'a,
335        S: serde::Serializer,
336        T: IntoIterator<Item = (&'a K, &'a V)>,
337        K: Serialize + 'a,
338        V: Serialize + 'a,
339    >(
340        target: T,
341        s: S,
342    ) -> Result<S::Ok, S::Error> {
343        let container: Vec<_> = target.into_iter().collect();
344        serde::Serialize::serialize(&container, s)
345    }
346
347    /// Serializes to a `Vec<(K, V)>` ordered by `key`, whatever order the container
348    /// iterates in.
349    pub fn serialize_sorted_to_vec_tuple<
350        'a,
351        S: serde::Serializer,
352        T: IntoIterator<Item = (&'a K, &'a V)>,
353        K: Serialize + 'a,
354        V: Serialize + 'a,
355        O: Ord,
356    >(
357        target: T,
358        key: impl Fn(&K) -> O,
359        s: S,
360    ) -> Result<S::Ok, S::Error> {
361        let mut container: Vec<_> = target.into_iter().collect();
362        container.sort_unstable_by_key(|(a, _)| key(a));
363        serde::Serialize::serialize(&container, s)
364    }
365
366    /// Deserializes from a `Vec<(K, V)>`.
367    ///
368    /// Useful for [`std::collections::HashMap`] with a non-string key,
369    /// which is unsupported by [`serde_json`](https://docs.rs/serde_json/).
370    pub fn deserialize_from_vec_tuple<
371        'de,
372        D: serde::Deserializer<'de>,
373        T: FromIterator<(K, V)>,
374        K: Deserialize<'de>,
375        V: Deserialize<'de>,
376    >(
377        d: D,
378    ) -> Result<T, D::Error> {
379        let hashmap_as_vec: Vec<(K, V)> = Deserialize::deserialize(d)?;
380        Ok(T::from_iter(hashmap_as_vec))
381    }
382
383    #[cfg(test)]
384    mod test {
385        use crate::alloc_prelude::*;
386        use std::collections::HashMap;
387
388        /// This test uses serde_json because json doesn't support non string
389        /// keys in hashmaps, which requires a custom serialization.
390        #[test]
391        fn serde_json_hashmap() {
392            #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
393            struct Test {
394                #[cfg_attr(
395                    feature = "serde-serialize",
396                    serde(
397                        serialize_with = "crate::utils::serde::serialize_to_vec_tuple",
398                        deserialize_with = "crate::utils::serde::deserialize_from_vec_tuple"
399                    )
400                )]
401                pub map: HashMap<usize, String>,
402            }
403
404            let s = Test {
405                map: [(42, "Forty-Two".to_string())].into(),
406            };
407            let j = serde_json::to_string(&s).unwrap();
408            assert_eq!(&j, "{\"map\":[[42,\"Forty-Two\"]]}");
409            let p: Test = serde_json::from_str(&j).unwrap();
410            assert_eq!(&p, &s);
411        }
412    }
413}