Skip to main content

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//! - [`SolverBodyIndex`]: A component storing the index of a body in the [`SolverBodies`] resource.
10//! - [`SolverBodies`]: A resource storing [`SolverBody`]s contiguously for awake, active bodies.
11
12mod plugin;
13
14pub use plugin::SolverBodyPlugin;
15
16use core::marker::PhantomData;
17
18use bevy::prelude::*;
19
20use super::Rot;
21#[cfg(feature = "3d")]
22use crate::prelude::ComputedAngularInertia;
23use crate::{SymmetricTensor, math::Vector, prelude::LockedAxes};
24
25// The `SolverBody` layout is inspired by `b2BodyState` in Box2D v3.
26
27/// Optimized rigid body state that the solver operates on,
28/// designed to improve memory locality and performance.
29///
30/// Only awake dynamic bodies and kinematic bodies have an associated solver body,
31/// stored contiguously in the [`SolverBodies`] resource and indexed by a [`SolverBodyIndex`]
32/// component on the body entity. Static bodies and sleeping dynamic bodies do not move,
33/// so they instead use a "dummy state" with [`SolverBody::default()`].
34///
35/// # Representation
36///
37/// The solver doesn't have access to the position or rotation of static or sleeping bodies,
38/// which is a problem when computing constraint anchors. To work around this, we have two options:
39///
40/// - **Option 1**: Use delta positions and rotations. This requires preparing
41///   base anchors and other necessary positional data in world space,
42///   and computing the updated anchors during substeps.
43/// - **Option 2**: Use full positions and rotations. This requires storing
44///   anchors in world space for static bodies and sleeping bodies,
45///   and in local space for dynamic bodies.
46///
47/// Avian uses **Option 1**, because:
48///
49/// - Using delta positions reduces round-off error when bodies are far from the origin.
50/// - Mixing world space and local space values depending on the body type would be
51///   quite confusing and error-prone, and would possibly require more branching.
52///
53/// In addition to the delta position and rotation, we also store the linear and angular velocities
54/// and some bitflags. This all fits in 32 bytes in 2D or 56 bytes in 3D.
55///
56/// The 2D data layout has been designed to support fast conversion to and from
57/// wide SIMD types via scatter/gather operations in the future when SIMD optimizations
58/// are implemented.
59// TODO: Is there a better layout for 3D?
60#[derive(Clone, Debug, Default, Reflect)]
61#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
63#[reflect(Debug)]
64pub struct SolverBody {
65    /// The linear velocity of the body.
66    ///
67    /// 8 bytes in 2D and 12 bytes in 3D.
68    pub linear_velocity: Vector,
69    /// The angular velocity of the body.
70    ///
71    /// 4 bytes in 2D and 12 bytes in 3D.
72    #[cfg(feature = "2d")]
73    pub angular_velocity: f32,
74    /// The angular velocity of the body.
75    ///
76    /// 8 bytes in 2D and 12 bytes in 3D.
77    #[cfg(feature = "3d")]
78    pub angular_velocity: Vector,
79    /// The change in position of the body.
80    ///
81    /// Stored as a delta to avoid round-off error when far from the origin.
82    ///
83    /// 8 bytes in 2D and 12 bytes in 3D.
84    pub delta_position: Vector,
85    /// The change in rotation of the body.
86    ///
87    /// Stored as a delta because the rotation of static bodies cannot be accessed
88    /// in the solver, but they have a known delta rotation of zero.
89    ///
90    /// 8 bytes in 2D and 16 bytes in 3D.
91    pub delta_rotation: Rot,
92    /// Flags for the body.
93    ///
94    /// 4 bytes.
95    pub flags: SolverBodyFlags,
96}
97
98impl SolverBody {
99    /// A dummy [`SolverBody`] for static bodies.
100    pub const DUMMY: Self = Self {
101        linear_velocity: Vector::ZERO,
102        #[cfg(feature = "2d")]
103        angular_velocity: 0.0,
104        #[cfg(feature = "3d")]
105        angular_velocity: Vector::ZERO,
106        delta_position: Vector::ZERO,
107        delta_rotation: Rot::IDENTITY,
108        flags: SolverBodyFlags::empty(),
109    };
110
111    /// Computes the velocity at the given `point` relative to the center of the body.
112    pub fn velocity_at_point(&self, point: Vector) -> Vector {
113        #[cfg(feature = "2d")]
114        {
115            self.linear_velocity + self.angular_velocity * point.perp()
116        }
117        #[cfg(feature = "3d")]
118        {
119            self.linear_velocity + self.angular_velocity.cross(point)
120        }
121    }
122
123    /// Returns `true` if gyroscopic motion is enabled for this body.
124    pub fn is_gyroscopic(&self) -> bool {
125        self.flags.contains(SolverBodyFlags::GYROSCOPIC_MOTION)
126    }
127}
128
129/// Flags for [`SolverBody`].
130#[repr(transparent)]
131#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
132#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
133#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
134#[reflect(Debug, PartialEq)]
135pub struct SolverBodyFlags(u32);
136
137bitflags::bitflags! {
138    impl SolverBodyFlags: u32 {
139        /// Set if translation along the `X` axis is locked.
140        const TRANSLATION_X_LOCKED = 0b100_000;
141        /// Set if translation along the `Y` axis is locked.
142        const TRANSLATION_Y_LOCKED = 0b010_000;
143        /// Set if translation along the `Z` axis is locked.
144        const TRANSLATION_Z_LOCKED = 0b001_000;
145        /// Set if rotation around the `X` axis is locked.
146        const ROTATION_X_LOCKED = 0b000_100;
147        /// Set if rotation around the `Y` axis is locked.
148        const ROTATION_Y_LOCKED = 0b000_010;
149        /// Set if rotation around the `Z` axis is locked.
150        const ROTATION_Z_LOCKED = 0b000_001;
151        /// Set if all translational axes are locked.
152        const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
153        /// Set if all rotational axes are locked.
154        const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
155        /// Set if all translational and rotational axes are locked.
156        const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
157        /// Set if the body is kinematic. Otherwise, it is dynamic.
158        const IS_KINEMATIC = 1 << 6;
159        /// Set if gyroscopic motion is enabled.
160        const GYROSCOPIC_MOTION = 1 << 7;
161        /// Set if the body has a custom position integration implementation.
162        const CUSTOM_POSITION_INTEGRATION = 1 << 8;
163        /// Set during the continuous collision stage if the body moved fast enough
164        /// to be treated as a "fast body" and have its motion swept. Transient.
165        const IS_FAST = 1 << 9;
166        /// Set during the continuous collision stage if the body's motion was stopped
167        /// at a time of impact. Transient.
168        const HAD_TIME_OF_IMPACT = 1 << 10;
169    }
170}
171
172impl SolverBodyFlags {
173    /// Returns the [`LockedAxes`] of the body.
174    pub fn locked_axes(&self) -> LockedAxes {
175        LockedAxes::from_bits(self.0 as u8)
176    }
177
178    /// Returns `true` if the body is dynamic.
179    pub fn is_dynamic(&self) -> bool {
180        !self.contains(SolverBodyFlags::IS_KINEMATIC)
181    }
182
183    /// Returns `true` if the body is kinematic.
184    pub fn is_kinematic(&self) -> bool {
185        self.contains(SolverBodyFlags::IS_KINEMATIC)
186    }
187}
188
189/*
190Box2D v3 stores mass and angular inertia in constraint data.
191For 2D, this is just 2 floats or 8 bytes for each body in each constraint.
192
193However, we also support 3D and locking translational axes, so our worst case
194would be *9* floats for each body, 3 for the effective mass vector
195and 6 for the symmetric 3x3 inertia tensor. Storing 36 bytes
196for each body in each constraint would be quite wasteful.
197
198Instead, we store a separate `SolverBodyInertia` struct for each `SolverBody`.
199The struct is optimized for memory locality and size.
200
201In 2D, we store the effective inertial properties directly:
202
203- Effective inverse mass (8 bytes)
204- Effective inverse angular inertia (4 bytes)
205- Flags (4 bytes)
206
207for a total of 16 bytes.
208
209In 3D, we instead compute the effective versions on the fly:
210
211- Inverse mass (4 bytes)
212- Inverse angular inertia (36 bytes, matrix with 9 floats)
213- Flags (4 bytes)
214
215for a total of 44 bytes. This will be 32 bytes in the future
216if/when we switch to a symmetric 3x3 matrix representation.
217
218The API abstracts over this difference in representation to reduce complexity.
219*/
220
221/// The inertial properties of a [`SolverBody`].
222///
223/// This includes the effective inverse mass and angular inertia,
224/// and flags indicating whether the body is static or has locked axes.
225///
226/// 16 bytes in 2D and 32 bytes in 3D.
227#[derive(Clone, Debug, Reflect)]
228#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
229#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
230#[reflect(Debug)]
231pub struct SolverBodyInertia {
232    /// The effective inverse mass of the body,
233    /// taking into account any locked axes.
234    ///
235    /// 8 bytes.
236    #[cfg(feature = "2d")]
237    effective_inv_mass: Vector,
238
239    /// The inverse mass of the body.
240    ///
241    /// 4 bytes.
242    #[cfg(feature = "3d")]
243    inv_mass: f32,
244
245    /// The effective inverse angular inertia of the body,
246    /// taking into account any locked axes.
247    ///
248    /// 4 bytes.
249    #[cfg(feature = "2d")]
250    effective_inv_angular_inertia: SymmetricTensor,
251
252    /// The world-space inverse angular inertia of the body.
253    ///
254    /// 32 bytes.
255    #[cfg(feature = "3d")]
256    effective_inv_angular_inertia: SymmetricTensor,
257
258    /// The [dominance] of the body.
259    ///
260    /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
261    /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
262    ///
263    /// 2 bytes.
264    ///
265    /// [dominance]: crate::dynamics::rigid_body::Dominance
266    /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
267    dominance: i16,
268
269    /// Flags indicating the inertial properties of the body,
270    /// like locked axes and whether the body is static.
271    ///
272    /// 2 bytes.
273    flags: InertiaFlags,
274}
275
276impl SolverBodyInertia {
277    /// A dummy [`SolverBodyInertia`] for static bodies.
278    pub const DUMMY: Self = Self {
279        #[cfg(feature = "2d")]
280        effective_inv_mass: Vector::ZERO,
281        #[cfg(feature = "3d")]
282        inv_mass: 0.0,
283        #[cfg(feature = "2d")]
284        effective_inv_angular_inertia: 0.0,
285        #[cfg(feature = "3d")]
286        effective_inv_angular_inertia: SymmetricTensor::ZERO,
287        dominance: i8::MAX as i16 + 1,
288        flags: InertiaFlags::STATIC,
289    };
290}
291
292impl Default for SolverBodyInertia {
293    fn default() -> Self {
294        Self::DUMMY
295    }
296}
297
298/// Flags indicating the inertial properties of a body.
299#[repr(transparent)]
300#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
301#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
302#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
303#[reflect(Debug, PartialEq)]
304pub struct InertiaFlags(u16);
305
306bitflags::bitflags! {
307    impl InertiaFlags: u16 {
308        /// Set if translation along the `X` axis is locked.
309        const TRANSLATION_X_LOCKED = 0b100_000;
310        /// Set if translation along the `Y` axis is locked.
311        const TRANSLATION_Y_LOCKED = 0b010_000;
312        /// Set if translation along the `Z` axis is locked.
313        const TRANSLATION_Z_LOCKED = 0b001_000;
314        /// Set if rotation around the `X` axis is locked.
315        const ROTATION_X_LOCKED = 0b000_100;
316        /// Set if rotation around the `Y` axis is locked.
317        const ROTATION_Y_LOCKED = 0b000_010;
318        /// Set if rotation around the `Z` axis is locked.
319        const ROTATION_Z_LOCKED = 0b000_001;
320        /// Set if all translational axes are locked.
321        const TRANSLATION_LOCKED = Self::TRANSLATION_X_LOCKED.bits() | Self::TRANSLATION_Y_LOCKED.bits() | Self::TRANSLATION_Z_LOCKED.bits();
322        /// Set if all rotational axes are locked.
323        const ROTATION_LOCKED = Self::ROTATION_X_LOCKED.bits() | Self::ROTATION_Y_LOCKED.bits() | Self::ROTATION_Z_LOCKED.bits();
324        /// Set if all translational and rotational axes are locked.
325        const ALL_LOCKED = Self::TRANSLATION_LOCKED.bits() | Self::ROTATION_LOCKED.bits();
326        /// Set if the body has infinite mass.
327        const INFINITE_MASS = 1 << 6;
328        /// Set if the body has infinite inertia.
329        const INFINITE_ANGULAR_INERTIA = 1 << 7;
330        /// Set if the body is static.
331        const STATIC = Self::INFINITE_MASS.bits() | Self::INFINITE_ANGULAR_INERTIA.bits();
332    }
333}
334
335impl InertiaFlags {
336    /// Returns the [`LockedAxes`] of the body.
337    pub fn locked_axes(&self) -> LockedAxes {
338        LockedAxes::from_bits(self.0 as u8)
339    }
340}
341
342impl SolverBodyInertia {
343    /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
344    /// and locked axes.
345    #[inline]
346    #[cfg(feature = "2d")]
347    pub fn new(
348        inv_mass: f32,
349        inv_inertia: SymmetricTensor,
350        locked_axes: LockedAxes,
351        dominance: i8,
352        is_dynamic: bool,
353    ) -> Self {
354        let mut effective_inv_mass = Vector::splat(inv_mass);
355        let mut effective_inv_angular_inertia = inv_inertia;
356        let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
357
358        if inv_mass == 0.0 {
359            flags |= InertiaFlags::INFINITE_MASS;
360        }
361        if inv_inertia == 0.0 {
362            flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
363        }
364
365        if locked_axes.is_translation_x_locked() {
366            effective_inv_mass.x = 0.0;
367        }
368        if locked_axes.is_translation_y_locked() {
369            effective_inv_mass.y = 0.0;
370        }
371        if locked_axes.is_rotation_locked() {
372            effective_inv_angular_inertia = 0.0;
373        }
374
375        Self {
376            effective_inv_mass,
377            effective_inv_angular_inertia,
378            dominance: if is_dynamic {
379                dominance as i16
380            } else {
381                i8::MAX as i16 + 1
382            },
383            flags: InertiaFlags(flags.0),
384        }
385    }
386
387    /// Creates a new [`SolverBodyInertia`] with the given mass, angular inertia,
388    /// and locked axes.
389    #[inline]
390    #[cfg(feature = "3d")]
391    pub fn new(
392        inv_mass: f32,
393        inv_inertia: SymmetricTensor,
394        locked_axes: LockedAxes,
395        dominance: i8,
396        is_dynamic: bool,
397    ) -> Self {
398        let mut effective_inv_angular_inertia = inv_inertia;
399        let mut flags = InertiaFlags(locked_axes.to_bits() as u16);
400
401        if inv_mass == 0.0 {
402            flags |= InertiaFlags::INFINITE_MASS;
403        }
404        if inv_inertia == SymmetricTensor::ZERO {
405            flags |= InertiaFlags::INFINITE_ANGULAR_INERTIA;
406        }
407
408        if locked_axes.is_rotation_x_locked() {
409            effective_inv_angular_inertia.m00 = 0.0;
410            effective_inv_angular_inertia.m01 = 0.0;
411            effective_inv_angular_inertia.m02 = 0.0;
412        }
413
414        if locked_axes.is_rotation_y_locked() {
415            effective_inv_angular_inertia.m01 = 0.0;
416            effective_inv_angular_inertia.m11 = 0.0;
417            effective_inv_angular_inertia.m12 = 0.0;
418        }
419
420        if locked_axes.is_rotation_z_locked() {
421            effective_inv_angular_inertia.m02 = 0.0;
422            effective_inv_angular_inertia.m12 = 0.0;
423            effective_inv_angular_inertia.m22 = 0.0;
424        }
425
426        Self {
427            inv_mass,
428            effective_inv_angular_inertia,
429            dominance: if is_dynamic {
430                dominance as i16
431            } else {
432                i8::MAX as i16 + 1
433            },
434            flags: InertiaFlags(flags.0),
435        }
436    }
437
438    /// Returns the effective inverse mass of the body,
439    /// taking into account any locked axes.
440    #[inline]
441    #[cfg(feature = "2d")]
442    pub fn effective_inv_mass(&self) -> Vector {
443        self.effective_inv_mass
444    }
445
446    /// Returns the effective inverse mass of the body,
447    /// taking into account any locked axes.
448    #[inline]
449    #[cfg(feature = "3d")]
450    pub fn effective_inv_mass(&self) -> Vector {
451        let mut inv_mass = Vector::splat(self.inv_mass);
452
453        if self.flags.contains(InertiaFlags::TRANSLATION_X_LOCKED) {
454            inv_mass.x = 0.0;
455        }
456        if self.flags.contains(InertiaFlags::TRANSLATION_Y_LOCKED) {
457            inv_mass.y = 0.0;
458        }
459        if self.flags.contains(InertiaFlags::TRANSLATION_Z_LOCKED) {
460            inv_mass.z = 0.0;
461        }
462
463        inv_mass
464    }
465
466    /// Returns the effective inverse angular inertia of the body,
467    /// taking into account any locked axes.
468    #[inline]
469    #[cfg(feature = "2d")]
470    pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
471        self.effective_inv_angular_inertia
472    }
473
474    /// Returns the effective inverse angular inertia of the body in world space,
475    /// taking into account any locked axes.
476    #[inline]
477    #[cfg(feature = "3d")]
478    pub fn effective_inv_angular_inertia(&self) -> SymmetricTensor {
479        self.effective_inv_angular_inertia
480    }
481
482    /// Updates the effective inverse angular inertia of the body in world space,
483    /// taking into account any locked axes.
484    #[inline]
485    #[cfg(feature = "3d")]
486    pub fn update_effective_inv_angular_inertia(
487        &mut self,
488        computed_angular_inertia: &ComputedAngularInertia,
489        rotation: Quat,
490    ) {
491        let locked_axes = self.flags.locked_axes();
492        let mut effective_inv_angular_inertia =
493            computed_angular_inertia.rotated(rotation).inverse();
494
495        if locked_axes.is_rotation_x_locked() {
496            effective_inv_angular_inertia.m00 = 0.0;
497            effective_inv_angular_inertia.m01 = 0.0;
498            effective_inv_angular_inertia.m02 = 0.0;
499        }
500
501        if locked_axes.is_rotation_y_locked() {
502            effective_inv_angular_inertia.m11 = 0.0;
503            effective_inv_angular_inertia.m01 = 0.0;
504            effective_inv_angular_inertia.m12 = 0.0;
505        }
506
507        if locked_axes.is_rotation_z_locked() {
508            effective_inv_angular_inertia.m22 = 0.0;
509            effective_inv_angular_inertia.m02 = 0.0;
510            effective_inv_angular_inertia.m12 = 0.0;
511        }
512
513        self.effective_inv_angular_inertia = effective_inv_angular_inertia;
514    }
515
516    /// Returns the [dominance] of the body.
517    ///
518    /// If the [`Dominance`] component is not specified, the default of `0` is returned for dynamic bodies.
519    /// For static and kinematic bodies, `i8::MAX + 1` (`128`) is always returned instead.
520    ///
521    /// [dominance]: crate::dynamics::rigid_body::Dominance
522    /// [`Dominance`]: crate::dynamics::rigid_body::Dominance
523    #[inline]
524    pub fn dominance(&self) -> i16 {
525        self.dominance
526    }
527
528    /// Returns the [`InertiaFlags`] of the body.
529    #[inline]
530    pub fn flags(&self) -> InertiaFlags {
531        self.flags
532    }
533}
534
535/// A component storing the index of an entity's [`SolverBody`] in the [`SolverBodies`] resource.
536///
537/// Only awake dynamic and kinematic bodies have an associated solver body.
538/// Static bodies and sleeping dynamic bodies do not have a solver body, and instead
539/// use a "dummy state" with [`SolverBody::default()`] and [`SolverBodyInertia::default()`].
540///
541/// This component is added, removed, and updated automatically by the solver plugin,
542/// and should not be modified by users.
543#[derive(Component, Clone, Copy, Debug, Deref, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
544#[reflect(Component, Debug, PartialEq, Hash)]
545#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
546#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
547pub struct SolverBodyIndex(pub u32);
548
549impl SolverBodyIndex {
550    /// An invalid index used to indicate that a body has no associated [`SolverBody`],
551    /// such as a static body. The solver substitutes a dummy state in this case.
552    pub const INVALID: Self = Self(u32::MAX);
553
554    /// Returns `true` if the index refers to a valid [`SolverBody`] in the [`SolverBodies`] resource.
555    #[inline]
556    pub fn is_valid(&self) -> bool {
557        self.0 != u32::MAX
558    }
559}
560
561impl Default for SolverBodyIndex {
562    fn default() -> Self {
563        Self::INVALID
564    }
565}
566
567/// A resource storing [solver bodies](SolverBody) and their [inertial properties](SolverBodyInertia)
568/// contiguously for awake, active rigid bodies.
569///
570/// Each entity with a solver body stores an index into this resource in a [`SolverBodyIndex`] component.
571#[derive(Resource, Default)]
572pub struct SolverBodies {
573    // TODO: Use `UniqueEntityVec`.
574    entities: Vec<Entity>,
575    bodies: Vec<SolverBody>,
576    inertias: Vec<SolverBodyInertia>,
577}
578
579impl SolverBodies {
580    /// Creates a new empty collection of solver bodies.
581    #[inline]
582    pub const fn new() -> Self {
583        Self {
584            entities: Vec::new(),
585            bodies: Vec::new(),
586            inertias: Vec::new(),
587        }
588    }
589
590    /// Returns the number of solver bodies.
591    #[inline]
592    pub fn len(&self) -> usize {
593        self.bodies.len()
594    }
595
596    /// Returns `true` if there are no solver bodies.
597    #[inline]
598    pub fn is_empty(&self) -> bool {
599        self.bodies.is_empty()
600    }
601
602    /// Returns a slice of the entities that have solver bodies.
603    #[inline]
604    pub fn entities(&self) -> &[Entity] {
605        &self.entities
606    }
607
608    /// Returns a slice of the solver bodies.
609    #[inline]
610    pub fn bodies(&self) -> &[SolverBody] {
611        &self.bodies
612    }
613
614    /// Returns a mutable slice of the solver bodies.
615    #[inline]
616    pub fn bodies_mut(&mut self) -> &mut [SolverBody] {
617        &mut self.bodies
618    }
619
620    /// Returns a slice of the solver body inertias.
621    #[inline]
622    pub fn inertias(&self) -> &[SolverBodyInertia] {
623        &self.inertias
624    }
625
626    /// Returns a mutable slice of the solver body inertias.
627    #[inline]
628    pub fn inertias_mut(&mut self) -> &mut [SolverBodyInertia] {
629        &mut self.inertias
630    }
631
632    /// Returns `true` if the given [`SolverBodyIndex`] refers to a body in this collection.
633    #[inline]
634    pub fn contains_index(&self, index: SolverBodyIndex) -> bool {
635        (index.0 as usize) < self.bodies.len()
636    }
637
638    /// Returns `true` if the given entity has a solver body in this collection.
639    #[inline]
640    pub fn contains_entity(&self, entity: Entity) -> bool {
641        self.entities.contains(&entity)
642    }
643
644    /// Returns the [`Entity`] associated with the given solver body index, if any.
645    #[inline]
646    pub fn get_entity(&self, index: SolverBodyIndex) -> Option<Entity> {
647        self.entities.get(index.0 as usize).copied()
648    }
649
650    /// Returns a reference to the [`SolverBody`] with the given index, if it exists.
651    #[inline]
652    pub fn get(&self, index: SolverBodyIndex) -> Option<&SolverBody> {
653        self.bodies.get(index.0 as usize)
654    }
655
656    /// Returns a mutable reference to the [`SolverBody`] with the given index, if it exists.
657    #[inline]
658    pub fn get_mut(&mut self, index: SolverBodyIndex) -> Option<&mut SolverBody> {
659        self.bodies.get_mut(index.0 as usize)
660    }
661
662    /// Returns a reference to the [`SolverBodyInertia`] with the given index, if it exists.
663    #[inline]
664    pub fn get_inertia(&self, index: SolverBodyIndex) -> Option<&SolverBodyInertia> {
665        self.inertias.get(index.0 as usize)
666    }
667
668    /// Returns a mutable reference to the [`SolverBodyInertia`] with the given index, if it exists.
669    #[inline]
670    pub fn get_inertia_mut(&mut self, index: SolverBodyIndex) -> Option<&mut SolverBodyInertia> {
671        self.inertias.get_mut(index.0 as usize)
672    }
673
674    /// Returns an iterator over mutable references to the solver bodies.
675    #[inline]
676    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SolverBody> {
677        self.bodies.iter_mut()
678    }
679
680    /// Adds a new solver body for the given entity, returning its [`SolverBodyIndex`].
681    #[inline]
682    pub fn push(
683        &mut self,
684        entity: Entity,
685        body: SolverBody,
686        inertia: SolverBodyInertia,
687    ) -> SolverBodyIndex {
688        let index = SolverBodyIndex(self.bodies.len() as u32);
689        self.entities.push(entity);
690        self.bodies.push(body);
691        self.inertias.push(inertia);
692        index
693    }
694
695    /// Removes the solver body with the given index, swapping in the last body to fill the gap.
696    ///
697    /// Returns the entity whose body was moved into `index`, so that its [`SolverBodyIndex`]
698    /// component can be updated. Returns `None` if the removed body was the last one.
699    ///
700    /// # Panics
701    ///
702    /// Panics if the index is out of bounds.
703    #[inline]
704    pub fn swap_remove(&mut self, index: SolverBodyIndex) -> Option<Entity> {
705        self.entities.swap_remove(index.0 as usize);
706        self.bodies.swap_remove(index.0 as usize);
707        self.inertias.swap_remove(index.0 as usize);
708        // TODO: Do this differently
709        // If the removed body was not the last one, a body was swapped into its slot.
710        self.entities.get(index.0 as usize).copied()
711    }
712
713    /// Clears all solver bodies.
714    #[inline]
715    pub fn clear(&mut self) {
716        self.entities.clear();
717        self.bodies.clear();
718        self.inertias.clear();
719    }
720
721    /// Returns a [`SolverBodiesAccess`] that allows obtaining mutable references to solver bodies
722    /// from parallel closures via raw pointers.
723    ///
724    /// This allows the solver to hand out mutable references to disjoint bodies concurrently,
725    /// which is sound as long as the caller ensures that no two closures access the same body
726    /// at the same time. This is guaranteed by constraint graph coloring.
727    #[inline]
728    pub fn access(&mut self) -> SolverBodiesAccess<'_> {
729        SolverBodiesAccess {
730            bodies: self.bodies.as_mut_ptr(),
731            inertias: self.inertias.as_mut_ptr(),
732            len: self.bodies.len(),
733            _marker: PhantomData,
734        }
735    }
736}
737
738/// Mutable disjoint access to the bodies and inertias of a [`SolverBodies`] resource
739/// for use inside the parallel solver.
740///
741/// The caller is responsible for ensuring that no two closures access the same body
742/// at the same time. This is guaranteed by constraint graph coloring and each
743/// [`SolverBodyIndex`] being unique to a single body.
744pub struct SolverBodiesAccess<'a> {
745    bodies: *mut SolverBody,
746    inertias: *mut SolverBodyInertia,
747    len: usize,
748    _marker: PhantomData<&'a mut SolverBodies>,
749}
750
751// SAFETY: The caller is responsible for only accessing disjoint indices concurrently.
752unsafe impl Send for SolverBodiesAccess<'_> {}
753// SAFETY: The caller is responsible for only accessing disjoint indices concurrently.
754unsafe impl Sync for SolverBodiesAccess<'_> {}
755
756impl SolverBodiesAccess<'_> {
757    /// Returns the number of solver bodies.
758    #[inline]
759    pub fn len(&self) -> usize {
760        self.len
761    }
762
763    /// Returns `true` if there are no solver bodies.
764    #[inline]
765    pub fn is_empty(&self) -> bool {
766        self.len == 0
767    }
768
769    /// Returns a mutable reference to the [`SolverBody`] with the given index.
770    ///
771    /// # Safety
772    ///
773    /// The index must be in bounds, and no other reference to the same body may be held concurrently.
774    #[inline]
775    #[expect(clippy::mut_from_ref)]
776    pub unsafe fn body_unchecked_mut(&self, index: SolverBodyIndex) -> &mut SolverBody {
777        debug_assert!((index.0 as usize) < self.len);
778        unsafe { &mut *self.bodies.add(index.0 as usize) }
779    }
780
781    /// Returns a mutable reference to the [`SolverBodyInertia`] with the given index.
782    ///
783    /// # Safety
784    ///
785    /// The index must be in bounds, and no other reference to the same inertia may be held concurrently.
786    #[inline]
787    #[expect(clippy::mut_from_ref)]
788    pub unsafe fn inertia_unchecked_mut(&self, index: SolverBodyIndex) -> &mut SolverBodyInertia {
789        debug_assert!((index.0 as usize) < self.len);
790        unsafe { &mut *self.inertias.add(index.0 as usize) }
791    }
792
793    /// Returns mutable body references and inertia references for the two given indices.
794    ///
795    /// An [`SolverBodyIndex::INVALID`] index yields `None` for that body, in which case the caller
796    /// should substitute [`SolverBody::DUMMY`] and [`SolverBodyInertia::DUMMY`].
797    ///
798    /// # Safety
799    ///
800    /// If both indices are valid, they must be different. Valid indices must be in bounds,
801    /// and no other references to the same bodies may be held concurrently.
802    #[inline]
803    #[expect(clippy::mut_from_ref)]
804    pub unsafe fn get_pair_unchecked_mut(
805        &self,
806        a: SolverBodyIndex,
807        b: SolverBodyIndex,
808    ) -> (
809        Option<(&mut SolverBody, &SolverBodyInertia)>,
810        Option<(&mut SolverBody, &SolverBodyInertia)>,
811    ) {
812        debug_assert!(!a.is_valid() || !b.is_valid() || a != b);
813        unsafe {
814            let first = a.is_valid().then(|| {
815                (
816                    &mut *self.bodies.add(a.0 as usize),
817                    &*self.inertias.add(a.0 as usize),
818                )
819            });
820            let second = b.is_valid().then(|| {
821                (
822                    &mut *self.bodies.add(b.0 as usize),
823                    &*self.inertias.add(b.0 as usize),
824                )
825            });
826            (first, second)
827        }
828    }
829}