avian3d/dynamics/solver/solver_body/
mod.rs

1//! Efficient rigid body definitions used by the performance-critical solver.
2//!
3//! This helps improve memory locality and makes random access faster for the constraint solver.
4//!
5//! This includes the following types:
6//!
7//! - [`SolverBody`]: The body state used by the solver.
8//! - [`SolverBodyInertia`]: The inertial properties of a body used by the solver.
9
10mod plugin;
11
12pub use plugin::SolverBodyPlugin;
13
14use bevy::prelude::*;
15
16use super::{Rotation, Vector};
17use crate::{SymmetricTensor, math::Scalar, prelude::LockedAxes};
18#[cfg(feature = "3d")]
19use crate::{math::Quaternion, prelude::ComputedAngularInertia};
20
21// The `SolverBody` layout is inspired by `b2BodyState` in Box2D v3.
22
23/// Optimized rigid body state that the solver operates on,
24/// designed to improve memory locality and performance.
25///
26/// Only awake dynamic bodies and kinematic bodies have an associated solver body,
27/// stored as a component on the body entity. Static bodies and sleeping dynamic bodies
28/// do not move, so they instead use a "dummy state" with [`SolverBody::default()`].
29///
30/// # Representation
31///
32/// The solver doesn't have access to the position or rotation of static or sleeping bodies,
33/// which is a problem when computing constraint anchors. To work around this, we have two options:
34///
35/// - **Option 1**: Use delta positions and rotations. This requires preparing
36///   base anchors and other necessary positional data in world space,
37///   and computing the updated anchors during substeps.
38/// - **Option 2**: Use full positions and rotations. This requires storing
39///   anchors in world space for static bodies and sleeping bodies,
40///   and in local space for dynamic bodies.
41///
42/// Avian uses **Option 1**, because:
43///
44/// - Using delta positions reduces round-off error when bodies are far from the origin.
45/// - Mixing world space and local space values depending on the body type would be
46///   quite confusing and error-prone, and would possibly require more branching.
47///
48/// In addition to the delta position and rotation, we also store the linear and angular velocities
49/// and some bitflags. This all fits in 32 bytes in 2D or 56 bytes in 3D with the `f32` feature.
50///
51/// The 2D data layout has been designed to support fast conversion to and from
52/// wide SIMD types via scatter/gather operations in the future when SIMD optimizations
53/// are implemented.
54// TODO: Is there a better layout for 3D?
55#[derive(Component, Clone, Debug, Default, Reflect)]
56#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
58#[reflect(Component, Debug)]
59pub struct SolverBody {
60    /// The linear velocity of the body.
61    ///
62    /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
63    pub linear_velocity: Vector,
64    /// The angular velocity of the body.
65    ///
66    /// 4 bytes in 2D and 12 bytes in 3D with the `f32` feature.
67    #[cfg(feature = "2d")]
68    pub angular_velocity: Scalar,
69    /// The angular velocity of the body.
70    ///
71    /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
72    #[cfg(feature = "3d")]
73    pub angular_velocity: Vector,
74    /// The change in position of the body.
75    ///
76    /// Stored as a delta to avoid round-off error when far from the origin.
77    ///
78    /// 8 bytes in 2D and 12 bytes in 3D with the `f32` feature.
79    pub delta_position: Vector,
80    /// The change in rotation of the body.
81    ///
82    /// Stored as a delta because the rotation of static bodies cannot be accessed
83    /// in the solver, but they have a known delta rotation of zero.
84    ///
85    /// 8 bytes in 2D and 16 bytes in 3D with the `f32` feature.
86    pub delta_rotation: Rotation,
87    /// Flags for the body.
88    ///
89    /// 4 bytes.
90    pub flags: SolverBodyFlags,
91}
92
93impl SolverBody {
94    /// A dummy [`SolverBody`] for static bodies.
95    pub const DUMMY: Self = Self {
96        linear_velocity: Vector::ZERO,
97        #[cfg(feature = "2d")]
98        angular_velocity: 0.0,
99        #[cfg(feature = "3d")]
100        angular_velocity: Vector::ZERO,
101        delta_position: Vector::ZERO,
102        delta_rotation: Rotation::IDENTITY,
103        flags: SolverBodyFlags::empty(),
104    };
105
106    /// Computes the velocity at the given `point` relative to the center of the body.
107    pub fn velocity_at_point(&self, point: Vector) -> Vector {
108        #[cfg(feature = "2d")]
109        {
110            self.linear_velocity + self.angular_velocity * point.perp()
111        }
112        #[cfg(feature = "3d")]
113        {
114            self.linear_velocity + self.angular_velocity.cross(point)
115        }
116    }
117
118    /// Returns `true` if gyroscopic motion is enabled for this body.
119    pub fn is_gyroscopic(&self) -> bool {
120        self.flags.contains(SolverBodyFlags::GYROSCOPIC_MOTION)
121    }
122}
123
124/// Flags for [`SolverBody`].
125#[repr(transparent)]
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
127#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
128#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
129#[reflect(Debug, PartialEq)]
130pub struct SolverBodyFlags(u32);
131
132bitflags::bitflags! {
133    impl SolverBodyFlags: u32 {
134        /// Set if translation along the `X` axis is locked.
135        const TRANSLATION_X_LOCKED = 0b100_000;
136        /// Set if translation along the `Y` axis is locked.
137        const TRANSLATION_Y_LOCKED = 0b010_000;
138        /// Set if translation along the `Z` axis is locked.
139        const TRANSLATION_Z_LOCKED = 0b001_000;
140        /// Set if rotation around the `X` axis is locked.
141        const ROTATION_X_LOCKED = 0b000_100;
142        /// Set if rotation around the `Y` axis is locked.
143        const ROTATION_Y_LOCKED = 0b000_010;
144        /// Set if rotation around the `Z` axis is locked.
145        const ROTATION_Z_LOCKED = 0b000_001;
146        /// Set if all translational axes are locked.
147        const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
148        /// Set if all rotational axes are locked.
149        const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
150        /// Set if all translational and rotational axes are locked.
151        const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
152        /// Set if the body is kinematic. Otherwise, it is dynamic.
153        const IS_KINEMATIC = 1 << 6;
154        /// Set if gyroscopic motion is enabled.
155        const GYROSCOPIC_MOTION = 1 << 7;
156    }
157}
158
159impl SolverBodyFlags {
160    /// Returns the [`LockedAxes`] of the body.
161    pub fn locked_axes(&self) -> LockedAxes {
162        LockedAxes::from_bits(self.0 as u8)
163    }
164
165    /// Returns `true` if the body is dynamic.
166    pub fn is_dynamic(&self) -> bool {
167        !self.contains(SolverBodyFlags::IS_KINEMATIC)
168    }
169
170    /// Returns `true` if the body is kinematic.
171    pub fn is_kinematic(&self) -> bool {
172        self.contains(SolverBodyFlags::IS_KINEMATIC)
173    }
174}
175
176/*
177Box2D v3 stores mass and angular inertia in constraint data.
178For 2D, this is just 2 floats or 8 bytes for each body in each constraint.
179
180However, we also support 3D and locking translational axes, so our worst case
181would be *9* floats for each body, 3 for the effective mass vector
182and 6 for the symmetric 3x3 inertia tensor. Storing 36 bytes
183for each body in each constraint would be quite wasteful.
184
185Instead, we store a separate `SolverBodyInertia` struct for each `SolverBody`.
186The struct is optimized for memory locality and size.
187
188In 2D, we store the effective inertial properties directly:
189
190- Effective inverse mass (8 bytes)
191- Effective inverse angular inertia (4 bytes)
192- Flags (4 bytes)
193
194for a total of 16 bytes.
195
196In 3D, we instead compute the effective versions on the fly:
197
198- Inverse mass (4 bytes)
199- Inverse angular inertia (36 bytes, matrix with 9 floats)
200- Flags (4 bytes)
201
202for a total of 44 bytes. This will be 32 bytes in the future
203if/when we switch to a symmetric 3x3 matrix representation.
204
205The API abstracts over this difference in representation to reduce complexity.
206*/
207
208/// The inertial properties of a [`SolverBody`].
209///
210/// This includes the effective inverse mass and angular inertia,
211/// and flags indicating whether the body is static or has locked axes.
212///
213/// 16 bytes in 2D and 32 bytes in 3D with the `f32` feature.
214#[derive(Component, Clone, Debug, Reflect)]
215#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
217#[reflect(Component, Debug)]
218pub struct SolverBodyInertia {
219    /// The effective inverse mass of the body,
220    /// taking into account any locked axes.
221    ///
222    /// 8 bytes with the `f32` feature.
223    #[cfg(feature = "2d")]
224    effective_inv_mass: Vector,
225
226    /// The inverse mass of the body.
227    ///
228    /// 4 bytes with the `f32` feature.
229    #[cfg(feature = "3d")]
230    inv_mass: Scalar,
231
232    /// The effective inverse angular inertia of the body,
233    /// taking into account any locked axes.
234    ///
235    /// 4 bytes with the `f32` feature.
236    #[cfg(feature = "2d")]
237    effective_inv_angular_inertia: SymmetricTensor,
238
239    /// The world-space inverse angular inertia of the body.
240    ///
241    /// 32 bytes with the `f32` feature.
242    #[cfg(feature = "3d")]
243    effective_inv_angular_inertia: SymmetricTensor,
244
245    /// The [dominance] of the body.
246    ///
247    /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
248    /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
249    ///
250    /// 2 bytes.
251    ///
252    /// [dominance]: crate::dynamics::rigid_body::Dominance
253    /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
254    dominance: i16,
255
256    /// Flags indicating the inertial properties of the body,
257    /// like locked axes and whether the body is static.
258    ///
259    /// 2 bytes.
260    flags: InertiaFlags,
261}
262
263impl SolverBodyInertia {
264    /// A dummy [`SolverBodyInertia`] for static bodies.
265    pub const DUMMY: Self = Self {
266        #[cfg(feature = "2d")]
267        effective_inv_mass: Vector::ZERO,
268        #[cfg(feature = "3d")]
269        inv_mass: 0.0,
270        #[cfg(feature = "2d")]
271        effective_inv_angular_inertia: 0.0,
272        #[cfg(feature = "3d")]
273        effective_inv_angular_inertia: SymmetricTensor::ZERO,
274        dominance: i8::MAX as i16 + 1,
275        flags: InertiaFlags::STATIC,
276    };
277}
278
279impl Default for SolverBodyInertia {
280    fn default() -> Self {
281        Self::DUMMY
282    }
283}
284
285/// Flags indicating the inertial properties of a body.
286#[repr(transparent)]
287#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
288#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
289#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
290#[reflect(Debug, PartialEq)]
291pub struct InertiaFlags(u16);
292
293bitflags::bitflags! {
294    impl InertiaFlags: u16 {
295        /// Set if translation along the `X` axis is locked.
296        const TRANSLATION_X_LOCKED = 0b100_000;
297        /// Set if translation along the `Y` axis is locked.
298        const TRANSLATION_Y_LOCKED = 0b010_000;
299        /// Set if translation along the `Z` axis is locked.
300        const TRANSLATION_Z_LOCKED = 0b001_000;
301        /// Set if rotation around the `X` axis is locked.
302        const ROTATION_X_LOCKED = 0b000_100;
303        /// Set if rotation around the `Y` axis is locked.
304        const ROTATION_Y_LOCKED = 0b000_010;
305        /// Set if rotation around the `Z` axis is locked.
306        const ROTATION_Z_LOCKED = 0b000_001;
307        /// Set if all translational axes are locked.
308        const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
309        /// Set if all rotational axes are locked.
310        const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
311        /// Set if all translational and rotational axes are locked.
312        const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
313        /// Set if the body has infinite mass.
314        const INFINITE_MASS = 1 << 6;
315        /// Set if the body has infinite inertia.
316        const INFINITE_ANGULAR_INERTIA = 1 << 7;
317        /// Set if the body is static.
318        const STATIC = Self::INFINITE_MASS.bits() | Self::INFINITE_ANGULAR_INERTIA.bits();
319    }
320}
321
322impl InertiaFlags {
323    /// Returns the [`LockedAxes`] of the body.
324    pub fn locked_axes(&self) -> LockedAxes {
325        LockedAxes::from_bits(self.0 as u8)
326    }
327}
328
329impl SolverBodyInertia {
330    /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
331    /// and locked axes.
332    #[inline]
333    #[cfg(feature = "2d")]
334    pub fn new(
335        inv_mass: Scalar,
336        inv_inertia: SymmetricTensor,
337        locked_axes: LockedAxes,
338        dominance: i8,
339        is_dynamic: bool,
340    ) -> Self {
341        let mut effective_inv_mass = Vector::splat(inv_mass);
342        let mut effective_inv_angular_inertia = inv_inertia;
343        let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
344
345        if inv_mass == 0.0 {
346            flags |= InertiaFlags::INFINITE_MASS;
347        }
348        if inv_inertia == 0.0 {
349            flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
350        }
351
352        if locked_axes.is_translation_x_locked() {
353            effective_inv_mass.x = 0.0;
354        }
355        if locked_axes.is_translation_y_locked() {
356            effective_inv_mass.y = 0.0;
357        }
358        if locked_axes.is_rotation_locked() {
359            effective_inv_angular_inertia = 0.0;
360        }
361
362        Self {
363            effective_inv_mass,
364            effective_inv_angular_inertia,
365            dominance: if is_dynamic {
366                dominance as i16
367            } else {
368                i8::MAX as i16 + 1
369            },
370            flags: InertiaFlags(flags.0),
371        }
372    }
373
374    /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
375    /// and locked axes.
376    #[inline]
377    #[cfg(feature = "3d")]
378    pub fn new(
379        inv_mass: Scalar,
380        inv_inertia: SymmetricTensor,
381        locked_axes: LockedAxes,
382        dominance: i8,
383        is_dynamic: bool,
384    ) -> Self {
385        let mut effective_inv_angular_inertia = inv_inertia;
386        let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
387
388        if inv_mass == 0.0 {
389            flags |= InertiaFlags::INFINITE_MASS;
390        }
391        if inv_inertia == SymmetricTensor::ZERO {
392            flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
393        }
394
395        if locked_axes.is_rotation_x_locked() {
396            effective_inv_angular_inertia.m00 = 0.0;
397            effective_inv_angular_inertia.m01 = 0.0;
398            effective_inv_angular_inertia.m02 = 0.0;
399        }
400
401        if locked_axes.is_rotation_y_locked() {
402            effective_inv_angular_inertia.m01 = 0.0;
403            effective_inv_angular_inertia.m11 = 0.0;
404            effective_inv_angular_inertia.m12 = 0.0;
405        }
406
407        if locked_axes.is_rotation_z_locked() {
408            effective_inv_angular_inertia.m02 = 0.0;
409            effective_inv_angular_inertia.m12 = 0.0;
410            effective_inv_angular_inertia.m22 = 0.0;
411        }
412
413        Self {
414            inv_mass,
415            effective_inv_angular_inertia,
416            dominance: if is_dynamic {
417                dominance as i16
418            } else {
419                i8::MAX as i16 + 1
420            },
421            flags: InertiaFlags(flags.0),
422        }
423    }
424
425    /// Returns the effective inverse mass of the body,
426    /// taking into account any locked axes.
427    #[inline]
428    #[cfg(feature = "2d")]
429    pub fn effective_inv_mass(&self) -> Vector {
430        self.effective_inv_mass
431    }
432
433    /// Returns the effective inverse mass of the body,
434    /// taking into account any locked axes.
435    #[inline]
436    #[cfg(feature = "3d")]
437    pub fn effective_inv_mass(&self) -> Vector {
438        let mut inv_mass = Vector::splat(self.inv_mass);
439
440        if self.flags.contains(InertiaFlags::TRANSLATION_X_LOCKED) {
441            inv_mass.x = 0.0;
442        }
443        if self.flags.contains(InertiaFlags::TRANSLATION_Y_LOCKED) {
444            inv_mass.y = 0.0;
445        }
446        if self.flags.contains(InertiaFlags::TRANSLATION_Z_LOCKED) {
447            inv_mass.z = 0.0;
448        }
449
450        inv_mass
451    }
452
453    /// Returns the effective inverse angular inertia of the body,
454    /// taking into account any locked axes.
455    #[inline]
456    #[cfg(feature = "2d")]
457    pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
458        self.effective_inv_angular_inertia
459    }
460
461    /// Returns the effective inverse angular inertia of the body in world space,
462    /// taking into account any locked axes.
463    #[inline]
464    #[cfg(feature = "3d")]
465    pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
466        self.effective_inv_angular_inertia
467    }
468
469    /// Updates the effective inverse angular inertia of the body in world space,
470    /// taking into account any locked axes.
471    #[inline]
472    #[cfg(feature = "3d")]
473    pub fn update_effective_inv_angular_inertia(
474        &mut self,
475        computed_angular_inertia: &ComputedAngularInertia,
476        rotation: Quaternion,
477    ) {
478        let locked_axes = self.flags.locked_axes();
479        let mut effective_inv_angular_inertia =
480            computed_angular_inertia.rotated(rotation).inverse();
481
482        if locked_axes.is_rotation_x_locked() {
483            effective_inv_angular_inertia.m00 = 0.0;
484            effective_inv_angular_inertia.m01 = 0.0;
485            effective_inv_angular_inertia.m02 = 0.0;
486        }
487
488        if locked_axes.is_rotation_y_locked() {
489            effective_inv_angular_inertia.m11 = 0.0;
490            effective_inv_angular_inertia.m01 = 0.0;
491            effective_inv_angular_inertia.m12 = 0.0;
492        }
493
494        if locked_axes.is_rotation_z_locked() {
495            effective_inv_angular_inertia.m22 = 0.0;
496            effective_inv_angular_inertia.m02 = 0.0;
497            effective_inv_angular_inertia.m12 = 0.0;
498        }
499
500        self.effective_inv_angular_inertia = effective_inv_angular_inertia;
501    }
502
503    /// Returns the [dominance] of the body.
504    ///
505    /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
506    /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
507    ///
508    /// [dominance]: crate::dynamics::rigid_body::Dominance
509    /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
510    #[inline]
511    pub fn dominance(&self) -> i16 {
512        self.dominance
513    }
514
515    /// Returns the [`InertiaFlags`] of the body.
516    #[inline]
517    pub fn flags(&self) -> InertiaFlags {
518        self.flags
519    }
520}