Skip to main content

rapier2d/dynamics/
rigid_body_components.rs

1#[cfg(doc)]
2use super::IntegrationParameters;
3use crate::alloc_prelude::*;
4use crate::control::PdErrors;
5#[cfg(doc)]
6use crate::control::PidController;
7use crate::dynamics::MassProperties;
8use crate::geometry::{
9    ColliderChanges, ColliderHandle, ColliderMassProps, ColliderParent, ColliderPosition,
10    ColliderSet, ColliderShape, ModifiedColliders,
11};
12use crate::math::{AngVector, AngularInertia, Pose, Real, Rotation, Vector};
13use crate::utils::{
14    AngularInertiaOps, CrossProduct, DotProduct, PoseOps, ScalarType, SimdRealCopy,
15};
16use num::Zero;
17#[cfg(feature = "dim2")]
18use parry::math::Rot2;
19
20/// The type of a body, governing the way it is affected by external forces.
21#[deprecated(note = "renamed as RigidBodyType")]
22pub type BodyStatus = RigidBodyType;
23
24#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
26/// The type of a rigid body, determining how it responds to forces and movement.
27pub enum RigidBodyType {
28    /// Fully simulated - responds to forces, gravity, and collisions.
29    ///
30    /// Use for: Falling objects, projectiles, physics-based characters, anything that should
31    /// behave realistically under physics simulation.
32    Dynamic = 0,
33
34    /// Never moves - has infinite mass and is unaffected by anything.
35    ///
36    /// Use for: Static level geometry, walls, floors, terrain, buildings.
37    Fixed = 1,
38
39    /// Controlled by setting next position - pushes but isn't pushed.
40    ///
41    /// You control this by setting where it should be next frame. Rapier computes the
42    /// velocity needed to get there. The body can push dynamic bodies but nothing can
43    /// push it back (one-way interaction).
44    ///
45    /// Use for: Animated platforms, objects controlled by external animation systems.
46    KinematicPositionBased = 2,
47
48    /// Controlled by setting velocity - pushes but isn't pushed.
49    ///
50    /// You control this by setting its velocity directly. It moves predictably regardless
51    /// of what it hits. Can push dynamic bodies but nothing can push it back (one-way interaction).
52    ///
53    /// Use for: Moving platforms, elevators, doors, player-controlled characters (when you want
54    /// direct control rather than physics-based movement).
55    KinematicVelocityBased = 3,
56    // Semikinematic, // A kinematic that performs automatic CCD with the fixed environment to avoid traversing it?
57    // Disabled,
58}
59
60impl RigidBodyType {
61    /// Is this rigid-body fixed (i.e. cannot move)?
62    pub fn is_fixed(self) -> bool {
63        self == RigidBodyType::Fixed
64    }
65
66    /// Is this rigid-body dynamic (i.e. can move and be affected by forces)?
67    pub fn is_dynamic(self) -> bool {
68        self == RigidBodyType::Dynamic
69    }
70
71    /// Is this rigid-body kinematic (i.e. can move but is unaffected by forces)?
72    pub fn is_kinematic(self) -> bool {
73        self == RigidBodyType::KinematicPositionBased
74            || self == RigidBodyType::KinematicVelocityBased
75    }
76
77    /// Is this rigid-body a dynamic rigid-body or a kinematic rigid-body?
78    ///
79    /// This method is mostly convenient internally where kinematic and dynamic rigid-body
80    /// are subject to the same behavior.
81    pub fn is_dynamic_or_kinematic(self) -> bool {
82        self != RigidBodyType::Fixed
83    }
84}
85
86bitflags::bitflags! {
87    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
88    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
89    /// Flags describing how the rigid-body has been modified by the user.
90    pub struct RigidBodyChanges: u32 {
91        /// Flag indicating that this rigid-body is in the modified rigid-body set.
92        const IN_MODIFIED_SET = 1 << 0;
93        /// Flag indicating that the `RigidBodyPosition` component of this rigid-body has been modified.
94        const POSITION    = 1 << 1;
95        /// Flag indicating that the `RigidBodyActivation` component of this rigid-body has been modified.
96        const SLEEP       = 1 << 2;
97        /// Flag indicating that the `RigidBodyColliders` component of this rigid-body has been modified.
98        const COLLIDERS   = 1 << 3;
99        /// Flag indicating that the `RigidBodyType` component of this rigid-body has been modified.
100        const TYPE        = 1 << 4;
101        /// Flag indicating that the `RigidBodyDominance` component of this rigid-body has been modified.
102        const DOMINANCE   = 1 << 5;
103        /// Flag indicating that the local mass-properties of this rigid-body must be recomputed.
104        const LOCAL_MASS_PROPERTIES = 1 << 6;
105        /// Flag indicating that the rigid-body was enabled or disabled.
106        const ENABLED_OR_DISABLED = 1 << 7;
107    }
108}
109
110impl Default for RigidBodyChanges {
111    fn default() -> Self {
112        RigidBodyChanges::empty()
113    }
114}
115
116#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
117#[derive(Clone, Debug, Copy, PartialEq)]
118/// The position of this rigid-body.
119pub struct RigidBodyPosition {
120    /// The world-space position of the rigid-body.
121    pub position: Pose,
122    /// The next position of the rigid-body.
123    ///
124    /// At the beginning of the timestep, and when the
125    /// timestep is complete we must have position == next_position
126    /// except for position-based kinematic bodies.
127    ///
128    /// The next_position is updated after the velocity and position
129    /// resolution. Then it is either validated (ie. we set position := set_position)
130    /// or clamped by CCD.
131    pub next_position: Pose,
132}
133
134impl Default for RigidBodyPosition {
135    fn default() -> Self {
136        Self {
137            position: Pose::IDENTITY,
138            next_position: Pose::IDENTITY,
139        }
140    }
141}
142
143impl RigidBodyPosition {
144    /// Computes the velocity need to travel from `self.position` to `self.next_position` in
145    /// a time equal to `1.0 / inv_dt`.
146    #[must_use]
147    pub fn interpolate_velocity(&self, inv_dt: Real, local_com: Vector) -> RigidBodyVelocity<Real> {
148        let pose_err = self.pose_errors(local_com);
149        RigidBodyVelocity {
150            linvel: pose_err.linear * inv_dt,
151            angvel: pose_err.angular * inv_dt,
152        }
153    }
154
155    /// Compute new positions after integrating the given forces and velocities.
156    ///
157    /// This uses a symplectic Euler integration scheme.
158    #[must_use]
159    pub fn integrate_forces_and_velocities(
160        &self,
161        dt: Real,
162        forces: &RigidBodyForces,
163        vels: &RigidBodyVelocity<Real>,
164        mprops: &RigidBodyMassProps,
165    ) -> Pose {
166        let new_vels = forces.integrate(dt, vels, mprops);
167        let local_com = mprops.local_mprops.local_com;
168        new_vels.integrate(dt, &self.position, &local_com)
169    }
170
171    /// Computes the difference between [`Self::next_position`] and [`Self::position`].
172    ///
173    /// This error measure can for example be used for interpolating the velocity between two poses,
174    /// or be given to the [`PidController`].
175    ///
176    /// Note that interpolating the velocity can be done more conveniently with
177    /// [`Self::interpolate_velocity`].
178    pub fn pose_errors(&self, local_com: Vector) -> PdErrors {
179        let com = self.position * local_com;
180        let shift = Pose::from_translation(com);
181        let dpos = shift.inverse() * self.next_position * self.position.inverse() * shift;
182
183        let angular;
184        #[cfg(feature = "dim2")]
185        {
186            angular = dpos.rotation.angle();
187        }
188        #[cfg(feature = "dim3")]
189        {
190            angular = dpos.rotation.to_scaled_axis();
191        }
192        let linear = dpos.translation;
193
194        PdErrors { linear, angular }
195    }
196}
197
198impl<T> From<T> for RigidBodyPosition
199where
200    Pose: From<T>,
201{
202    fn from(position: T) -> Self {
203        let position = position.into();
204        Self {
205            position,
206            next_position: position,
207        }
208    }
209}
210
211bitflags::bitflags! {
212    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
213    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
214    /// Flags affecting the behavior of the constraints solver for a given contact manifold.
215    pub struct AxesMask: u8 {
216        /// The translational X axis.
217        const LIN_X = 1 << 0;
218        /// The translational Y axis.
219        const LIN_Y = 1 << 1;
220        /// The translational Z axis.
221        #[cfg(feature = "dim3")]
222        const LIN_Z = 1 << 2;
223        /// The rotational X axis.
224        #[cfg(feature = "dim3")]
225        const ANG_X = 1 << 3;
226        /// The rotational Y axis.
227        #[cfg(feature = "dim3")]
228        const ANG_Y = 1 << 4;
229        /// The rotational Z axis.
230        const ANG_Z = 1 << 5;
231    }
232}
233
234impl Default for AxesMask {
235    fn default() -> Self {
236        AxesMask::empty()
237    }
238}
239
240bitflags::bitflags! {
241    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
242    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
243    /// Flags that lock specific movement axes to prevent translation or rotation.
244    ///
245    /// Use this to constrain body movement to specific directions/axes. Common uses:
246    /// - **2D games in 3D**: Lock Z translation and X/Y rotation to keep everything in the XY plane
247    /// - **Upright characters**: Lock rotations to prevent tipping over
248    /// - **Sliding objects**: Lock rotation while allowing translation
249    /// - **Spinning objects**: Lock translation while allowing rotation
250    ///
251    /// # Example
252    /// ```
253    /// # use rapier3d::prelude::*;
254    /// # let mut bodies = RigidBodySet::new();
255    /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic());
256    /// # let body = bodies.get_mut(body_handle).unwrap();
257    /// // Character that can't tip over (rotation locked, but can move)
258    /// body.set_locked_axes(LockedAxes::ROTATION_LOCKED, true);
259    ///
260    /// // Object that slides but doesn't rotate
261    /// body.set_locked_axes(LockedAxes::ROTATION_LOCKED, true);
262    ///
263    /// // 2D game in 3D engine (lock Z movement and X/Y rotation)
264    /// body.set_locked_axes(
265    ///     LockedAxes::TRANSLATION_LOCKED_Z |
266    ///     LockedAxes::ROTATION_LOCKED_X |
267    ///     LockedAxes::ROTATION_LOCKED_Y,
268    ///     true
269    /// );
270    /// ```
271    pub struct LockedAxes: u8 {
272        /// Prevents movement along the X axis.
273        const TRANSLATION_LOCKED_X = 1 << 0;
274        /// Prevents movement along the Y axis.
275        const TRANSLATION_LOCKED_Y = 1 << 1;
276        /// Prevents movement along the Z axis.
277        const TRANSLATION_LOCKED_Z = 1 << 2;
278        /// Prevents all translational movement.
279        const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits();
280        /// Prevents rotation around the X axis.
281        const ROTATION_LOCKED_X = 1 << 3;
282        /// Prevents rotation around the Y axis.
283        const ROTATION_LOCKED_Y = 1 << 4;
284        /// Prevents rotation around the Z axis.
285        const ROTATION_LOCKED_Z = 1 << 5;
286        /// Prevents all rotational movement.
287        const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits() | Self::ROTATION_LOCKED_Y.bits() | Self::ROTATION_LOCKED_Z.bits();
288    }
289}
290
291/// Mass and angular inertia added to a rigid-body on top of its attached colliders’ contributions.
292#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
293#[derive(Copy, Clone, Debug, PartialEq)]
294pub enum RigidBodyAdditionalMassProps {
295    /// Mass properties to be added as-is.
296    MassProps(MassProperties),
297    /// Mass to be added to the rigid-body. This will also automatically scale
298    /// the attached colliders total angular inertia to account for the added mass.
299    Mass(Real),
300}
301
302impl Default for RigidBodyAdditionalMassProps {
303    fn default() -> Self {
304        RigidBodyAdditionalMassProps::MassProps(MassProperties::default())
305    }
306}
307
308#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
309#[derive(Clone, Debug, PartialEq)]
310// #[repr(C)]
311/// The mass properties of a rigid-body.
312pub struct RigidBodyMassProps {
313    /// The world-space center of mass of the rigid-body.
314    pub world_com: Vector,
315    /// The inverse mass taking into account translation locking.
316    pub effective_inv_mass: Vector,
317    /// The square-root of the world-space inverse angular inertia tensor of the rigid-body,
318    /// taking into account rotation locking.
319    pub effective_world_inv_inertia: AngularInertia,
320    /// The local mass properties of the rigid-body.
321    pub local_mprops: MassProperties,
322    /// Flags for locking rotation and translation.
323    pub flags: LockedAxes,
324    /// Mass-properties of this rigid-bodies, added to the contributions of its attached colliders.
325    pub additional_local_mprops: Option<Box<RigidBodyAdditionalMassProps>>,
326    /// Conservative bound on the distance of any shape point from the local center of mass;
327    /// the sleep metric and the CCD fast-body criterion use it to turn angular velocity into
328    /// a farthest-point speed. Refreshed with the mass properties; `0` for collider-less bodies.
329    #[cfg_attr(feature = "serde-serialize", serde(default))]
330    pub(crate) max_extent: Real,
331}
332
333impl Default for RigidBodyMassProps {
334    fn default() -> Self {
335        Self {
336            flags: LockedAxes::empty(),
337            local_mprops: MassProperties::zero(),
338            additional_local_mprops: None,
339            world_com: Vector::ZERO,
340            effective_inv_mass: Vector::ZERO,
341            effective_world_inv_inertia: AngularInertia::zero(),
342            max_extent: 0.0,
343        }
344    }
345}
346
347impl From<LockedAxes> for RigidBodyMassProps {
348    fn from(flags: LockedAxes) -> Self {
349        Self {
350            flags,
351            ..Self::default()
352        }
353    }
354}
355
356impl From<MassProperties> for RigidBodyMassProps {
357    fn from(local_mprops: MassProperties) -> Self {
358        Self {
359            local_mprops,
360            ..Default::default()
361        }
362    }
363}
364
365impl RigidBodyMassProps {
366    /// The mass of the rigid-body.
367    #[must_use]
368    pub fn mass(&self) -> Real {
369        crate::utils::inv(self.local_mprops.inv_mass)
370    }
371
372    /// The effective mass (that takes the potential translation locking into account) of
373    /// this rigid-body.
374    #[must_use]
375    pub fn effective_mass(&self) -> Vector {
376        self.effective_inv_mass.map(crate::utils::inv)
377    }
378
379    /// The square root of the effective world-space angular inertia (that takes the potential rotation locking into account) of
380    /// this rigid-body.
381    #[must_use]
382    pub fn effective_angular_inertia(&self) -> AngularInertia {
383        #[allow(unused_mut)] // mut needed in 3D.
384        let mut ang_inertia = self.effective_world_inv_inertia;
385
386        // Make the matrix invertible.
387        #[cfg(feature = "dim3")]
388        {
389            if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
390                ang_inertia.m11 = 1.0;
391            }
392            if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
393                ang_inertia.m22 = 1.0;
394            }
395            if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
396                ang_inertia.m33 = 1.0;
397            }
398        }
399
400        #[allow(unused_mut)] // mut needed in 3D.
401        let mut result = ang_inertia.inverse();
402
403        // Remove the locked axes again.
404        #[cfg(feature = "dim3")]
405        {
406            if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
407                result.m11 = 0.0;
408            }
409            if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
410                result.m22 = 0.0;
411            }
412            if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
413                result.m33 = 0.0;
414            }
415        }
416
417        result
418    }
419
420    /// Recompute the mass-properties of this rigid-bodies based on its currently attached colliders.
421    pub fn recompute_mass_properties_from_colliders(
422        &mut self,
423        colliders: &ColliderSet,
424        attached_colliders: &RigidBodyColliders,
425        body_type: RigidBodyType,
426        position: &Pose,
427    ) {
428        let added_mprops = self
429            .additional_local_mprops
430            .as_ref()
431            .map(|mprops| **mprops)
432            .unwrap_or_else(|| RigidBodyAdditionalMassProps::MassProps(MassProperties::default()));
433
434        self.local_mprops = MassProperties::default();
435
436        for handle in &attached_colliders.0 {
437            if let Some(co) = colliders.get(*handle) {
438                if co.is_enabled() {
439                    if let Some(co_parent) = co.parent {
440                        let to_add = co
441                            .mprops
442                            .mass_properties(&*co.shape)
443                            .transform_by(&co_parent.pos_wrt_parent);
444                        self.local_mprops += to_add;
445                    }
446                }
447            }
448        }
449
450        match added_mprops {
451            RigidBodyAdditionalMassProps::MassProps(mprops) => {
452                self.local_mprops += mprops;
453            }
454            RigidBodyAdditionalMassProps::Mass(mass) => {
455                let prev_mass = self.local_mprops.mass();
456                if prev_mass > 0.0 {
457                    self.local_mprops.set_mass(prev_mass + mass, true);
458                } else {
459                    // The colliders contribute no mass, so `set_mass` has no angular
460                    // inertia to rescale and the body could never rotate. Derive it (and the
461                    // CoM) from the shapes at unit density, rescaled to the additional mass.
462                    let mut unit_mprops = MassProperties::default();
463                    for handle in &attached_colliders.0 {
464                        if let Some(co) = colliders.get(*handle) {
465                            if co.is_enabled() {
466                                if let Some(co_parent) = co.parent {
467                                    unit_mprops += co
468                                        .shape
469                                        .mass_properties(1.0)
470                                        .transform_by(&co_parent.pos_wrt_parent);
471                                }
472                            }
473                        }
474                    }
475
476                    if unit_mprops.mass() > 0.0 {
477                        unit_mprops.set_mass(mass, true);
478                        self.local_mprops += unit_mprops;
479                    } else {
480                        // No shape to derive an inertia from: just set the mass.
481                        self.local_mprops.set_mass(mass, true);
482                    }
483                }
484            }
485        }
486
487        self.recompute_max_extent(colliders, attached_colliders);
488        self.update_world_mass_properties(body_type, position);
489    }
490
491    /// Refreshes [`Self::max_extent`] from the attached colliders' bounding
492    /// spheres, measured about the local center of mass.
493    pub(crate) fn recompute_max_extent(
494        &mut self,
495        colliders: &ColliderSet,
496        attached_colliders: &RigidBodyColliders,
497    ) {
498        let local_com = self.local_mprops.local_com;
499        let mut max_extent: Real = 0.0;
500        for handle in &attached_colliders.0 {
501            if let Some(co) = colliders.get(*handle) {
502                if co.is_enabled() {
503                    if let Some(co_parent) = co.parent {
504                        let sphere = co
505                            .shape
506                            .compute_local_bounding_sphere()
507                            .transform_by(&co_parent.pos_wrt_parent);
508                        let extent = (sphere.center - local_com).length() + sphere.radius;
509                        max_extent = max_extent.max(extent);
510                    }
511                }
512            }
513        }
514        self.max_extent = max_extent;
515    }
516
517    /// Conservative bound on the distance of any point of the body's shapes
518    /// from its local center of mass. `0` for collider-less bodies.
519    ///
520    /// Used by the sleep metric and the CCD fast-body criterion to turn angular
521    /// velocity into a farthest-point speed.
522    #[inline]
523    pub fn max_extent(&self) -> Real {
524        self.max_extent
525    }
526
527    /// Update the world-space mass properties of `self`, taking into account the new position.
528    pub fn update_world_mass_properties(&mut self, body_type: RigidBodyType, position: &Pose) {
529        self.world_com = self.local_mprops.world_com(position);
530        self.effective_inv_mass = Vector::splat(self.local_mprops.inv_mass);
531        self.effective_world_inv_inertia = self.local_mprops.world_inv_inertia(&position.rotation);
532
533        // Take into account translation/rotation locking.
534        if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_X) {
535            self.effective_inv_mass.x = 0.0;
536        }
537
538        if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Y) {
539            self.effective_inv_mass.y = 0.0;
540        }
541
542        #[cfg(feature = "dim3")]
543        if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Z) {
544            self.effective_inv_mass.z = 0.0;
545        }
546
547        #[cfg(feature = "dim2")]
548        {
549            if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
550                self.effective_world_inv_inertia = 0.0;
551            }
552        }
553        #[cfg(feature = "dim3")]
554        {
555            if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
556                self.effective_world_inv_inertia.m11 = 0.0;
557                self.effective_world_inv_inertia.m12 = 0.0;
558                self.effective_world_inv_inertia.m13 = 0.0;
559            }
560
561            if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
562                self.effective_world_inv_inertia.m22 = 0.0;
563                self.effective_world_inv_inertia.m12 = 0.0;
564                self.effective_world_inv_inertia.m23 = 0.0;
565            }
566            if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
567                self.effective_world_inv_inertia.m33 = 0.0;
568                self.effective_world_inv_inertia.m13 = 0.0;
569                self.effective_world_inv_inertia.m23 = 0.0;
570            }
571        }
572    }
573}
574
575#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
576#[derive(Clone, Debug, Copy, PartialEq)]
577/// The velocities of this rigid-body.
578// repr(C): `as_vector` reinterprets this struct as a flat vector with the
579// linear part first, so the field order must be guaranteed.
580#[repr(C)]
581pub struct RigidBodyVelocity<T: ScalarType> {
582    /// The linear velocity of the rigid-body.
583    pub linvel: T::Vector,
584    /// The angular velocity of the rigid-body.
585    pub angvel: T::AngVector,
586}
587
588impl Default for RigidBodyVelocity<Real> {
589    fn default() -> Self {
590        Self::zero()
591    }
592}
593
594impl RigidBodyVelocity<Real> {
595    /// Create a new rigid-body velocity component.
596    #[must_use]
597    #[cfg(feature = "dim2")]
598    pub fn new(linvel: Vector, angvel: AngVector) -> Self {
599        Self { linvel, angvel }
600    }
601
602    /// Create a new rigid-body velocity component.
603    #[must_use]
604    #[cfg(feature = "dim3")]
605    pub fn new(linvel: Vector, angvel: AngVector) -> Self {
606        Self {
607            linvel: Vector::new(linvel.x, linvel.y, linvel.z),
608            angvel: AngVector::new(angvel.x, angvel.y, angvel.z),
609        }
610    }
611
612    /// Converts a slice to a rigid-body velocity.
613    ///
614    /// The slice must contain at least 3 elements: the `slice[0..2]` contains
615    /// the linear velocity and the `slice[2]` contains the angular velocity.
616    #[must_use]
617    #[cfg(feature = "dim2")]
618    pub fn from_slice(slice: &[Real]) -> Self {
619        Self {
620            linvel: Vector::new(slice[0], slice[1]),
621            angvel: slice[2],
622        }
623    }
624
625    /// Converts a slice to a rigid-body velocity.
626    ///
627    /// The slice must contain at least 6 elements: the `slice[0..3]` contains
628    /// the linear velocity and the `slice[3..6]` contains the angular velocity.
629    #[must_use]
630    #[cfg(feature = "dim3")]
631    pub fn from_slice(slice: &[Real]) -> Self {
632        Self {
633            linvel: Vector::new(slice[0], slice[1], slice[2]),
634            angvel: AngVector::new(slice[3], slice[4], slice[5]),
635        }
636    }
637
638    /// Velocities set to zero.
639    #[must_use]
640    pub fn zero() -> Self {
641        Self {
642            linvel: Default::default(),
643            angvel: Default::default(),
644        }
645    }
646
647    /// Are both the linear and angular velocities finite (neither NaN nor infinite)?
648    #[must_use]
649    pub fn is_finite(&self) -> bool {
650        self.linvel.is_finite() && self.angvel.is_finite()
651    }
652
653    /// This velocity seen as a slice.
654    ///
655    /// The linear part is stored first.
656    #[inline]
657    pub fn as_slice(&self) -> &[Real] {
658        self.as_vector().as_slice()
659    }
660
661    /// This velocity seen as a mutable slice.
662    ///
663    /// The linear part is stored first.
664    #[inline]
665    pub fn as_mut_slice(&mut self) -> &mut [Real] {
666        self.as_vector_mut().as_mut_slice()
667    }
668
669    /// This velocity seen as a vector.
670    ///
671    /// The linear part is stored first.
672    #[inline]
673    #[cfg(feature = "dim2")]
674    pub fn as_vector(&self) -> &na::Vector3<Real> {
675        unsafe { core::mem::transmute(self) }
676    }
677
678    /// This velocity seen as a mutable vector.
679    ///
680    /// The linear part is stored first.
681    #[inline]
682    #[cfg(feature = "dim2")]
683    pub fn as_vector_mut(&mut self) -> &mut na::Vector3<Real> {
684        unsafe { core::mem::transmute(self) }
685    }
686
687    /// This velocity seen as a vector.
688    ///
689    /// The linear part is stored first.
690    #[inline]
691    #[cfg(feature = "dim3")]
692    pub fn as_vector(&self) -> &na::Vector6<Real> {
693        unsafe { core::mem::transmute(self) }
694    }
695
696    /// This velocity seen as a mutable vector.
697    ///
698    /// The linear part is stored first.
699    #[inline]
700    #[cfg(feature = "dim3")]
701    pub fn as_vector_mut(&mut self) -> &mut na::Vector6<Real> {
702        unsafe { core::mem::transmute(self) }
703    }
704
705    /// Return `self` rotated by `rotation`.
706    #[must_use]
707    #[cfg(feature = "dim2")]
708    pub fn transformed(self, rotation: &Rotation) -> Self {
709        Self {
710            linvel: *rotation * self.linvel,
711            angvel: self.angvel,
712        }
713    }
714
715    /// Return `self` rotated by `rotation`.
716    #[must_use]
717    #[cfg(feature = "dim3")]
718    pub fn transformed(self, rotation: &Rotation) -> Self {
719        Self {
720            linvel: *rotation * self.linvel,
721            angvel: *rotation * self.angvel,
722        }
723    }
724
725    /// The approximate kinetic energy of this rigid-body.
726    ///
727    /// This approximation does not take the rigid-body's mass and angular inertia
728    /// into account. Some physics engines call this the "mass-normalized kinetic
729    /// energy".
730    #[must_use]
731    pub fn pseudo_kinetic_energy(&self) -> Real {
732        0.5 * (self.linvel.length_squared() + self.angvel.gdot(self.angvel))
733    }
734
735    /// The velocity of the given world-space point on this rigid-body.
736    #[must_use]
737    #[cfg(feature = "dim2")]
738    pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
739        let dpt = point - world_com;
740        self.linvel + self.angvel.gcross(dpt)
741    }
742
743    /// The velocity of the given world-space point on this rigid-body.
744    #[must_use]
745    #[cfg(feature = "dim3")]
746    pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
747        let dpt = point - world_com;
748        self.linvel + self.angvel.gcross(dpt)
749    }
750
751    /// Are these velocities exactly equal to zero?
752    #[must_use]
753    pub fn is_zero(&self) -> bool {
754        self.linvel == Vector::ZERO && self.angvel == AngVector::default()
755    }
756
757    /// The kinetic energy of this rigid-body.
758    #[must_use]
759    #[profiling::function]
760    pub fn kinetic_energy(&self, rb_mprops: &RigidBodyMassProps) -> Real {
761        let mut energy = (rb_mprops.mass() * self.linvel.length_squared()) / 2.0;
762
763        #[cfg(feature = "dim2")]
764        if !num::Zero::is_zero(&rb_mprops.effective_world_inv_inertia) {
765            let inertia = 1.0 / rb_mprops.effective_world_inv_inertia;
766            energy += inertia * self.angvel * self.angvel / 2.0;
767        }
768
769        #[cfg(feature = "dim3")]
770        if !rb_mprops.effective_world_inv_inertia.is_zero() {
771            let inertia = rb_mprops.effective_world_inv_inertia.inverse_unchecked();
772            energy += self.angvel.gdot(inertia * self.angvel) / 2.0;
773        }
774
775        energy
776    }
777
778    /// Applies an impulse at the center-of-mass of this rigid-body.
779    /// The impulse is applied right away, changing the linear velocity.
780    /// This does nothing on non-dynamic bodies.
781    pub fn apply_impulse(&mut self, rb_mprops: &RigidBodyMassProps, impulse: Vector) {
782        self.linvel += impulse * rb_mprops.effective_inv_mass;
783    }
784
785    /// Applies an angular impulse at the center-of-mass of this rigid-body.
786    /// The impulse is applied right away, changing the angular velocity.
787    /// This does nothing on non-dynamic bodies.
788    #[cfg(feature = "dim2")]
789    pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Real) {
790        self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
791    }
792
793    /// Applies an angular impulse at the center-of-mass of this rigid-body.
794    /// The impulse is applied right away, changing the angular velocity.
795    /// This does nothing on non-dynamic bodies.
796    #[cfg(feature = "dim3")]
797    pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Vector) {
798        self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
799    }
800
801    /// Applies an impulse at the given world-space point of this rigid-body.
802    /// The impulse is applied right away, changing the linear and/or angular velocities.
803    /// This does nothing on non-dynamic bodies.
804    #[cfg(feature = "dim2")]
805    pub fn apply_impulse_at_point(
806        &mut self,
807        rb_mprops: &RigidBodyMassProps,
808        impulse: Vector,
809        point: Vector,
810    ) {
811        let torque_impulse = (point - rb_mprops.world_com).perp_dot(impulse);
812        self.apply_impulse(rb_mprops, impulse);
813        self.apply_torque_impulse(rb_mprops, torque_impulse);
814    }
815
816    /// Applies an impulse at the given world-space point of this rigid-body.
817    /// The impulse is applied right away, changing the linear and/or angular velocities.
818    /// This does nothing on non-dynamic bodies.
819    #[cfg(feature = "dim3")]
820    pub fn apply_impulse_at_point(
821        &mut self,
822        rb_mprops: &RigidBodyMassProps,
823        impulse: Vector,
824        point: Vector,
825    ) {
826        let torque_impulse = (point - rb_mprops.world_com).cross(impulse);
827        self.apply_impulse(rb_mprops, impulse);
828        self.apply_torque_impulse(rb_mprops, torque_impulse);
829    }
830}
831
832impl<T: ScalarType> RigidBodyVelocity<T> {
833    /// Returns the update velocities after applying the given damping.
834    #[must_use]
835    pub fn apply_damping(&self, dt: T, damping: &RigidBodyDamping<T>) -> Self {
836        let one = T::one();
837        RigidBodyVelocity {
838            linvel: self.linvel * (one / (one + dt * damping.linear_damping)),
839            angvel: self.angvel * (one / (one + dt * damping.angular_damping)),
840        }
841    }
842
843    /// Integrate the velocities in `self` to compute obtain new positions when moving from the given
844    /// initial position `init_pos`.
845    #[must_use]
846    #[inline]
847    #[allow(clippy::let_and_return)] // Keeping `result` binding for potential renormalization
848    pub fn integrate(&self, dt: T, init_pos: &T::Pose, local_com: &T::Vector) -> T::Pose {
849        let com = *init_pos * *local_com;
850        let result = init_pos
851            .append_translation(-com)
852            .append_rotation(self.angvel * dt)
853            .append_translation(com + self.linvel * dt);
854        // TODO: is renormalization really useful?
855        // result.rotation.renormalize_fast();
856        result
857    }
858}
859
860impl RigidBodyVelocity<Real> {
861    /// Same as [`Self::integrate`] but with the angular part linearized and the local
862    /// center-of-mass assumed to be zero.
863    #[inline]
864    #[cfg(feature = "dim2")]
865    pub(crate) fn integrate_linearized(
866        &self,
867        dt: Real,
868        translation: &mut Vector,
869        rotation: &mut Rotation,
870    ) {
871        let dang = self.angvel * dt;
872        let new_cos = rotation.re - dang * rotation.im;
873        let new_sin = rotation.im + dang * rotation.re;
874        *rotation = Rot2::from_cos_sin_unchecked(new_cos, new_sin);
875        // NOTE: don't use renormalize_fast since the linearization might cause more drift.
876        rotation.normalize_mut();
877        *translation += self.linvel * dt;
878    }
879
880    /// Same as [`Self::integrate`] but with the angular part linearized and the local
881    /// center-of-mass assumed to be zero.
882    #[inline]
883    #[cfg(feature = "dim3")]
884    pub(crate) fn integrate_linearized(
885        &self,
886        dt: Real,
887        translation: &mut Vector,
888        rotation: &mut Rotation,
889    ) {
890        // Rotations linearization is inspired from
891        // https://ahrs.readthedocs.io/en/latest/filters/angular.html (not using the matrix form).
892        let hang = self.angvel * (dt * 0.5);
893        // Quaternion identity + `hang` seen as a quaternion.
894        let id_plus_hang = Rotation::from_xyzw(hang.x, hang.y, hang.z, 1.0);
895        *rotation = id_plus_hang * *rotation;
896        *rotation = rotation.normalize();
897        *translation += self.linvel * dt;
898    }
899}
900
901impl core::ops::Mul<Real> for RigidBodyVelocity<Real> {
902    type Output = Self;
903
904    fn mul(self, rhs: Real) -> Self {
905        RigidBodyVelocity {
906            linvel: self.linvel * rhs,
907            angvel: self.angvel * rhs,
908        }
909    }
910}
911
912impl core::ops::Add<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
913    type Output = Self;
914
915    fn add(self, rhs: Self) -> Self {
916        RigidBodyVelocity {
917            linvel: self.linvel + rhs.linvel,
918            angvel: self.angvel + rhs.angvel,
919        }
920    }
921}
922
923impl core::ops::AddAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
924    fn add_assign(&mut self, rhs: Self) {
925        self.linvel += rhs.linvel;
926        self.angvel += rhs.angvel;
927    }
928}
929
930impl core::ops::Sub<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
931    type Output = Self;
932
933    fn sub(self, rhs: Self) -> Self {
934        RigidBodyVelocity {
935            linvel: self.linvel - rhs.linvel,
936            angvel: self.angvel - rhs.angvel,
937        }
938    }
939}
940
941impl core::ops::SubAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
942    fn sub_assign(&mut self, rhs: Self) {
943        self.linvel -= rhs.linvel;
944        self.angvel -= rhs.angvel;
945    }
946}
947
948#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
949#[derive(Clone, Debug, Copy, PartialEq)]
950/// Damping factors to progressively slow down a rigid-body.
951pub struct RigidBodyDamping<T> {
952    /// Damping factor for gradually slowing down the translational motion of the rigid-body.
953    pub linear_damping: T,
954    /// Damping factor for gradually slowing down the angular motion of the rigid-body.
955    pub angular_damping: T,
956}
957
958impl<T: SimdRealCopy> Default for RigidBodyDamping<T> {
959    fn default() -> Self {
960        Self {
961            linear_damping: T::zero(),
962            angular_damping: T::zero(),
963        }
964    }
965}
966
967#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
968#[derive(Clone, Debug, Copy, PartialEq)]
969/// The user-defined external forces applied to this rigid-body.
970pub struct RigidBodyForces {
971    /// Accumulation of external forces (only for dynamic bodies).
972    pub force: Vector,
973    /// Accumulation of external torques (only for dynamic bodies).
974    pub torque: AngVector,
975    /// Gravity is multiplied by this scaling factor before it's
976    /// applied to this rigid-body.
977    pub gravity_scale: Real,
978    /// Forces applied by the user.
979    pub user_force: Vector,
980    /// Torque applied by the user.
981    pub user_torque: AngVector,
982    /// Are gyroscopic forces enabled for this rigid-body?
983    #[cfg(feature = "dim3")]
984    pub gyroscopic_forces_enabled: bool,
985}
986
987impl Default for RigidBodyForces {
988    fn default() -> Self {
989        #[cfg(feature = "dim2")]
990        return Self {
991            force: Vector::ZERO,
992            torque: 0.0,
993            gravity_scale: 1.0,
994            user_force: Vector::ZERO,
995            user_torque: 0.0,
996        };
997
998        #[cfg(feature = "dim3")]
999        return Self {
1000            force: Vector::ZERO,
1001            torque: AngVector::ZERO,
1002            gravity_scale: 1.0,
1003            user_force: Vector::ZERO,
1004            user_torque: AngVector::ZERO,
1005            gyroscopic_forces_enabled: true,
1006        };
1007    }
1008}
1009
1010impl RigidBodyForces {
1011    /// Integrate these forces to compute new velocities.
1012    #[must_use]
1013    pub fn integrate(
1014        &self,
1015        dt: Real,
1016        init_vels: &RigidBodyVelocity<Real>,
1017        mprops: &RigidBodyMassProps,
1018    ) -> RigidBodyVelocity<Real> {
1019        let linear_acc = self.force * mprops.effective_inv_mass;
1020        let angular_acc = mprops.effective_world_inv_inertia * self.torque;
1021
1022        RigidBodyVelocity {
1023            linvel: init_vels.linvel + linear_acc * dt,
1024            angvel: init_vels.angvel + angular_acc * dt,
1025        }
1026    }
1027
1028    /// Adds to `self` the gravitational force that would result in a gravitational acceleration
1029    /// equal to `gravity`.
1030    pub fn compute_effective_force_and_torque(&mut self, gravity: Vector, mass: Vector) {
1031        self.force = self.user_force + gravity * mass * self.gravity_scale;
1032        self.torque = self.user_torque;
1033    }
1034
1035    /// Applies a force at the given world-space point of the rigid-body with the given mass properties.
1036    pub fn apply_force_at_point(
1037        &mut self,
1038        rb_mprops: &RigidBodyMassProps,
1039        force: Vector,
1040        point: Vector,
1041    ) {
1042        self.user_force += force;
1043        self.user_torque += (point - rb_mprops.world_com).gcross(force);
1044    }
1045}
1046
1047#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1048#[derive(Clone, Debug, Copy, PartialEq)]
1049/// Information used for Continuous-Collision-Detection.
1050pub struct RigidBodyCcd {
1051    /// The distance used by the CCD solver to decide if a movement would
1052    /// result in a tunnelling problem.
1053    pub ccd_thickness: Real,
1054    /// Is CCD active for this rigid-body?
1055    ///
1056    /// Set automatically for any **dynamic** body moving fast enough to tunnel (regardless of
1057    /// `self.ccd_enabled`): it then sweeps fixed colliders, or all bodies if `ccd_enabled` is set too.
1058    pub ccd_active: bool,
1059    /// Is full ("bullet") CCD enabled for this rigid-body?
1060    ///
1061    /// Fast dynamic bodies always sweep *fixed* colliders; `true` upgrades this body to also
1062    /// sweep kinematic and dynamic bodies.
1063    pub ccd_enabled: bool,
1064    /// The soft-CCD prediction distance for this rigid-body.
1065    pub soft_ccd_prediction: Real,
1066    /// Allow this body to exceed the angular speed cap.
1067    ///
1068    /// By default angular velocity is clamped each substep to ~45°/step to keep CCD reliable;
1069    /// set `true` for bodies that must spin fast (e.g. wheels).
1070    pub allow_fast_rotation: bool,
1071}
1072
1073impl Default for RigidBodyCcd {
1074    fn default() -> Self {
1075        Self {
1076            ccd_thickness: Real::MAX,
1077            ccd_active: false,
1078            ccd_enabled: false,
1079            soft_ccd_prediction: 0.0,
1080            allow_fast_rotation: false,
1081        }
1082    }
1083}
1084
1085impl RigidBodyCcd {
1086    /// The maximum velocity any point of any collider attached to this rigid-body
1087    /// moving with the given velocity can have.
1088    ///
1089    /// `max_extent` is the body's farthest collider point distance from its center of
1090    /// mass ([`RigidBodyMassProps::max_extent`]).
1091    pub fn max_point_velocity(&self, vels: &RigidBodyVelocity<Real>, max_extent: Real) -> Real {
1092        #[cfg(feature = "dim2")]
1093        return vels.linvel.length() + vels.angvel.abs() * max_extent;
1094        #[cfg(feature = "dim3")]
1095        return vels.linvel.length() + vels.angvel.length() * max_extent;
1096    }
1097
1098    /// Is this rigid-body moving fast enough so that it may cause a tunneling problem?
1099    ///
1100    /// The fast-body criterion: fast when the farthest point of its colliders can move more
1101    /// than half the body’s thinnest extent (`ccd_thickness`) within one timestep.
1102    pub fn is_moving_fast(
1103        &self,
1104        dt: Real,
1105        vels: &RigidBodyVelocity<Real>,
1106        forces: Option<&RigidBodyForces>,
1107        max_extent: Real,
1108    ) -> bool {
1109        let max_point_velocity = if let Some(forces) = forces {
1110            let linear_part = (vels.linvel + forces.force * dt).length();
1111            #[cfg(feature = "dim2")]
1112            let angular_part = (vels.angvel + forces.torque * dt).abs() * max_extent;
1113            #[cfg(feature = "dim3")]
1114            let angular_part = (vels.angvel + forces.torque * dt).length() * max_extent;
1115            linear_part + angular_part
1116        } else {
1117            self.max_point_velocity(vels, max_extent)
1118        };
1119
1120        max_point_velocity * dt > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1121    }
1122
1123    /// The fast-body safety factor: a body is fast when it can move more than half its
1124    /// thinnest extent in one step.
1125    pub const FAST_BODY_SAFETY_FACTOR: Real = 0.5;
1126
1127    /// The fast-body criterion evaluated on the actual solved motion of this step.
1128    ///
1129    /// `pos` must hold the solved `next_position`; the test uses the larger of the actual pose
1130    /// delta and the velocity-based estimate.
1131    pub fn is_moving_fast_with_next_position(
1132        &self,
1133        dt: Real,
1134        vels: &RigidBodyVelocity<Real>,
1135        pos: &RigidBodyPosition,
1136        local_com: Vector,
1137        max_extent: Real,
1138    ) -> bool {
1139        let com1 = pos.position * local_com;
1140        let com2 = pos.next_position * local_com;
1141
1142        // Rotation contribution to the moved distance of the farthest point:
1143        // 2D: |sin(Δθ)| · maxExtent; 3D: 2·|Δq.v| · maxExtent ≈ Δθ · maxExtent.
1144        let delta_rot = pos.next_position.rotation * pos.position.rotation.inverse();
1145        #[cfg(feature = "dim2")]
1146        let angular_delta = delta_rot.sin().abs() * max_extent;
1147        #[cfg(feature = "dim3")]
1148        let angular_delta =
1149            2.0 * Vector::new(delta_rot.x, delta_rot.y, delta_rot.z).length() * max_extent;
1150
1151        let max_delta_position = (com2 - com1).length() + angular_delta;
1152        let max_velocity = self.max_point_velocity(vels, max_extent);
1153        let max_motion = max_delta_position.max(max_velocity * dt);
1154
1155        max_motion > Self::FAST_BODY_SAFETY_FACTOR * self.ccd_thickness
1156    }
1157}
1158
1159#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1160#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
1161/// Internal identifiers used by the physics engine.
1162pub struct RigidBodyIds {
1163    pub(crate) active_island_id: u32,
1164    pub(crate) active_set_id: u32,
1165    /// The persistent island this body belongs to ([`crate::dynamics::INVALID_ISLAND`] for fixed
1166    /// or disabled bodies).
1167    pub(crate) island_id: u32,
1168    /// This body's index in its persistent island's `bodies` array (also its
1169    /// union-find node id during an island split).
1170    pub(crate) island_index: u32,
1171}
1172
1173impl Default for RigidBodyIds {
1174    fn default() -> Self {
1175        Self {
1176            active_island_id: u32::MAX,
1177            active_set_id: u32::MAX,
1178            island_id: crate::dynamics::INVALID_ISLAND,
1179            island_index: u32::MAX,
1180        }
1181    }
1182}
1183
1184#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1185#[derive(Default, Clone, Debug, PartialEq, Eq)]
1186/// The set of colliders attached to this rigid-bodies.
1187///
1188/// This should not be modified manually unless you really know what
1189/// you are doing (for example if you are trying to integrate Rapier
1190/// to a game engine using its component-based interface).
1191pub struct RigidBodyColliders(pub Vec<ColliderHandle>);
1192
1193impl RigidBodyColliders {
1194    /// Detach a collider from this rigid-body.
1195    pub fn detach_collider(
1196        &mut self,
1197        rb_changes: &mut RigidBodyChanges,
1198        co_handle: ColliderHandle,
1199    ) {
1200        if let Some(i) = self.0.iter().position(|e| *e == co_handle) {
1201            rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1202            self.0.swap_remove(i);
1203        }
1204    }
1205
1206    /// Attach a collider to this rigid-body.
1207    pub fn attach_collider(
1208        &mut self,
1209        rb_type: RigidBodyType,
1210        rb_changes: &mut RigidBodyChanges,
1211        rb_ccd: &mut RigidBodyCcd,
1212        rb_mprops: &mut RigidBodyMassProps,
1213        rb_pos: &RigidBodyPosition,
1214        co_handle: ColliderHandle,
1215        co_pos: &mut ColliderPosition,
1216        co_parent: &ColliderParent,
1217        co_shape: &ColliderShape,
1218        co_mprops: &ColliderMassProps,
1219    ) {
1220        rb_changes.set(RigidBodyChanges::COLLIDERS, true);
1221
1222        co_pos.0 = rb_pos.position * co_parent.pos_wrt_parent;
1223        // Shapes the continuous phase never sweeps (meshes, heightfields, polylines, voxels)
1224        // don't count toward CCD thickness: a trimesh's zero `ccd_thickness` would flag the body
1225        // as fast every step for a sweep that never happens.
1226        if !crate::dynamics::ccd::shape_never_ccd_swept(&**co_shape) {
1227            rb_ccd.ccd_thickness = rb_ccd.ccd_thickness.min(co_shape.ccd_thickness());
1228        }
1229
1230        let mass_properties = co_mprops
1231            .mass_properties(&**co_shape)
1232            .transform_by(&co_parent.pos_wrt_parent);
1233        self.0.push(co_handle);
1234        rb_mprops.local_mprops += mass_properties;
1235        rb_mprops.update_world_mass_properties(rb_type, &rb_pos.position);
1236    }
1237
1238    /// Update the positions of all the colliders attached to this rigid-body.
1239    pub(crate) fn update_positions(
1240        &self,
1241        colliders: &mut ColliderSet,
1242        modified_colliders: &mut ModifiedColliders,
1243        parent_pos: &Pose,
1244    ) {
1245        for handle in &self.0 {
1246            // NOTE: the ColliderParent component must exist if we enter this method.
1247            // NOTE: currently, we are propagating the position even if the collider is disabled.
1248            //       Is that the best behavior?
1249            let co = colliders.index_mut_internal(*handle);
1250            let new_pos = parent_pos * co.parent.as_ref().unwrap().pos_wrt_parent;
1251
1252            // Set the modification flag so we can benefit from the modification-tracking
1253            // when updating the narrow-phase/broad-phase afterwards.
1254            modified_colliders.push_once(*handle, co);
1255
1256            co.changes |= ColliderChanges::POSITION;
1257            co.pos = ColliderPosition(new_pos);
1258        }
1259    }
1260}
1261
1262#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1263#[derive(Default, Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1264/// The dominance groups of a rigid-body.
1265pub struct RigidBodyDominance(pub i8);
1266
1267impl RigidBodyDominance {
1268    /// The actual dominance group of this rigid-body, after taking into account its type.
1269    pub fn effective_group(&self, status: &RigidBodyType) -> i16 {
1270        if status.is_dynamic_or_kinematic() {
1271            self.0 as i16
1272        } else {
1273            i8::MAX as i16 + 1
1274        }
1275    }
1276}
1277
1278/// Controls when a body goes to sleep (becomes inactive to save CPU).
1279///
1280/// ## Sleeping System
1281///
1282/// Bodies automatically sleep when they're at rest, dramatically improving performance
1283/// in scenes with many inactive objects. Sleeping bodies are:
1284/// - Excluded from simulation (no collision detection, no velocity integration)
1285/// - Automatically woken when disturbed (hit by moving object, connected via joint)
1286/// - Woken manually with `body.wake_up()` or `islands.wake_up()`
1287///
1288/// ## When to disable sleeping
1289///
1290/// Most bodies should sleep! Only disable if the body needs to stay active despite being still:
1291/// - Bodies you frequently query for raycasts/contacts
1292/// - Bodies with time-based behaviors while stationary
1293///
1294/// Use `RigidBodyBuilder::can_sleep(false)` or `RigidBodyActivation::cannot_sleep()`.
1295#[derive(Copy, Clone, Debug, PartialEq)]
1296#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1297pub struct RigidBodyActivation {
1298    /// Velocity threshold for sleeping (scaled by `length_unit`).
1299    ///
1300    /// Compared against the body's farthest-point speed (`|linvel| + |angvel| * max_extent`)
1301    /// and against its farthest-point displacement rate (so solver position
1302    /// corrections count as motion too). If negative, body never sleeps. Default: 0.05 units/second.
1303    pub normalized_linear_threshold: Real,
1304
1305    /// Angular velocity threshold for sleeping (radians/second).
1306    ///
1307    /// For bodies with colliders, angular motion is folded into the point-velocity check of
1308    /// `normalized_linear_threshold`; this raw threshold only applies to collider-less bodies.
1309    /// If negative, body never sleeps. Default: 0.5 rad/s.
1310    pub angular_threshold: Real,
1311
1312    /// How long the body must stay below the velocity threshold before sleeping (seconds).
1313    ///
1314    /// Default: 0.5 seconds.
1315    pub time_until_sleep: Real,
1316
1317    /// Internal timer tracking how long body has been still.
1318    pub time_since_can_sleep: Real,
1319
1320    /// Is this body currently sleeping?
1321    pub sleeping: bool,
1322
1323    /// Pose at the previous step: the sleep check measures actual per-step displacement,
1324    /// solver position corrections included (those never show up in the velocities).
1325    pub(crate) sleep_prev_pose: Pose,
1326}
1327
1328impl Default for RigidBodyActivation {
1329    fn default() -> Self {
1330        Self::active()
1331    }
1332}
1333
1334impl RigidBodyActivation {
1335    /// The default linear velocity below which a body can be put to sleep.
1336    ///
1337    /// Default: `0.05` length units per second.
1338    pub fn default_normalized_linear_threshold() -> Real {
1339        0.05
1340    }
1341
1342    /// The default angular velocity below which a body can be put to sleep.
1343    pub fn default_angular_threshold() -> Real {
1344        0.5
1345    }
1346
1347    /// The amount of time the rigid-body must remain below it’s linear and angular velocity
1348    /// threshold before falling to sleep.
1349    ///
1350    /// Default: half a second.
1351    pub fn default_time_until_sleep() -> Real {
1352        0.5
1353    }
1354
1355    /// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
1356    pub fn active() -> Self {
1357        RigidBodyActivation {
1358            normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1359            angular_threshold: Self::default_angular_threshold(),
1360            time_until_sleep: Self::default_time_until_sleep(),
1361            time_since_can_sleep: 0.0,
1362            sleeping: false,
1363            sleep_prev_pose: Pose::IDENTITY,
1364        }
1365    }
1366
1367    /// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive.
1368    pub fn inactive() -> Self {
1369        RigidBodyActivation {
1370            normalized_linear_threshold: Self::default_normalized_linear_threshold(),
1371            angular_threshold: Self::default_angular_threshold(),
1372            time_until_sleep: Self::default_time_until_sleep(),
1373            time_since_can_sleep: Self::default_time_until_sleep(),
1374            sleeping: true,
1375            sleep_prev_pose: Pose::IDENTITY,
1376        }
1377    }
1378
1379    /// Create a new activation status that prevents the rigid-body from sleeping.
1380    pub fn cannot_sleep() -> Self {
1381        RigidBodyActivation {
1382            normalized_linear_threshold: -1.0,
1383            angular_threshold: -1.0,
1384            ..Self::active()
1385        }
1386    }
1387
1388    /// Returns `true` if the body is not asleep.
1389    #[inline]
1390    pub fn is_active(&self) -> bool {
1391        !self.sleeping
1392    }
1393
1394    /// Wakes up this rigid-body.
1395    #[inline]
1396    pub fn wake_up(&mut self, strong: bool) {
1397        self.sleeping = false;
1398
1399        if strong {
1400            self.time_since_can_sleep = 0.0;
1401        }
1402    }
1403
1404    /// Put this rigid-body to sleep.
1405    #[inline]
1406    pub fn sleep(&mut self) {
1407        self.sleeping = true;
1408        self.time_since_can_sleep = self.time_until_sleep;
1409    }
1410
1411    /// Does this body have a sufficiently low kinetic energy for a long enough
1412    /// duration to be eligible for sleeping?
1413    pub fn is_eligible_for_sleep(&self) -> bool {
1414        self.time_since_can_sleep >= self.time_until_sleep
1415    }
1416
1417    pub(crate) fn update_energy(
1418        &mut self,
1419        body_type: RigidBodyType,
1420        length_unit: Real,
1421        sq_linvel: Real,
1422        sq_angvel: Real,
1423        max_extent: Real,
1424        pose: &Pose,
1425        dt: Real,
1426    ) {
1427        // A manual `RigidBody::sleep()` pins sleep eligibility until something wakes the
1428        // body: the velocity/drift gates must not cancel it (a teleport right before
1429        // sleeping trips the drift gate, keeping the body simulated while flagged asleep).
1430        if self.sleeping {
1431            self.time_since_can_sleep = self.time_until_sleep;
1432            return;
1433        }
1434
1435        let can_sleep = match body_type {
1436            RigidBodyType::Dynamic => {
1437                let linear_threshold = self.normalized_linear_threshold * length_unit;
1438                let prev_pose = core::mem::replace(&mut self.sleep_prev_pose, *pose);
1439                let angular_ok = if max_extent > 0.0 {
1440                    use crate::num::FloatConst;
1441                    // Use a fixed angular threshold that unambiguously imply movement.
1442                    // The position-based criteria will be more restrictive, but we keep
1443                    // this for the rare case where the orientation’s periodicity would
1444                    // make the pose drift estimate too approximate.
1445                    self.angular_threshold >= 0.0
1446                        && sq_angvel < Real::FRAC_PI_2() * Real::FRAC_PI_2()
1447                } else {
1448                    // Collider-less bodies have `max_extent == 0` so we need to take its
1449                    // angular velocity into account since the pose delta cannot take its
1450                    // rotation into account.
1451                    sq_angvel < self.angular_threshold * self.angular_threshold.abs()
1452                };
1453
1454                let drift = crate::geometry::relative_pose_drift(&prev_pose, pose, max_extent);
1455                angular_ok && drift * 0.5 < linear_threshold * dt
1456            }
1457            RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased => {
1458                // Platforms only sleep if both velocities are exactly zero. If it’s not exactly
1459                // zero, then the user really wants them to move.
1460                sq_linvel == 0.0 && sq_angvel == 0.0
1461            }
1462            RigidBodyType::Fixed => true,
1463        };
1464
1465        if can_sleep {
1466            self.time_since_can_sleep += dt;
1467        } else {
1468            self.time_since_can_sleep = 0.0;
1469        }
1470    }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    use super::*;
1476    use crate::math::Real;
1477
1478    #[test]
1479    fn test_interpolate_velocity() {
1480        // Interpolate and then integrate the velocity to see if
1481        // the end positions match.
1482        #[cfg(feature = "f32")]
1483        let mut rng = oorandom::Rand32::new(0);
1484        #[cfg(feature = "f64")]
1485        let mut rng = oorandom::Rand64::new(0);
1486
1487        for i in -10..=10 {
1488            let mult = i as Real;
1489            let (local_com, curr_pos, next_pos);
1490            #[cfg(feature = "dim2")]
1491            {
1492                local_com = Vector::new(rng.rand_float(), rng.rand_float());
1493                curr_pos = Pose::new(
1494                    Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1495                    rng.rand_float(),
1496                );
1497                next_pos = Pose::new(
1498                    Vector::new(rng.rand_float(), rng.rand_float()) * mult,
1499                    rng.rand_float(),
1500                );
1501            }
1502            #[cfg(feature = "dim3")]
1503            {
1504                local_com = Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float());
1505                curr_pos = Pose::new(
1506                    Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1507                    Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1508                );
1509                next_pos = Pose::new(
1510                    Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
1511                    Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
1512                );
1513            }
1514
1515            let dt = 0.016;
1516            let rb_pos = RigidBodyPosition {
1517                position: curr_pos,
1518                next_position: next_pos,
1519            };
1520            let vel = rb_pos.interpolate_velocity(1.0 / dt, local_com);
1521            let interp_pos = vel.integrate(dt, &curr_pos, &local_com);
1522            approx::assert_relative_eq!(interp_pos, next_pos, epsilon = 1.0e-5);
1523        }
1524    }
1525
1526    /// Runs `steps` of `update_energy` on a body that only ever moves by `shift_per_step`
1527    /// (zero velocity: the motion stands for solver position corrections).
1528    fn creep(shift_per_step: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1529        let mut activation = RigidBodyActivation::active();
1530        let mut shift = 0.0;
1531
1532        for _ in 0..steps {
1533            shift += shift_per_step;
1534            let pose = Pose::from_translation(Vector::X * shift);
1535            activation.update_energy(RigidBodyType::Dynamic, 1.0, 0.0, 0.0, 1.0, &pose, dt);
1536        }
1537
1538        activation
1539    }
1540
1541    /// Runs `steps` of `update_energy` on a body pinned to one pose while reporting `linvel`,
1542    /// like a body in a loaded stack whose contacts cancel its velocity again every step.
1543    fn pinned_with_velocity(linvel: Real, steps: usize, dt: Real) -> RigidBodyActivation {
1544        let mut activation = RigidBodyActivation::active();
1545        let pose = Pose::from_translation(Vector::X * 3.0);
1546
1547        for _ in 0..steps {
1548            activation.update_energy(
1549                RigidBodyType::Dynamic,
1550                1.0,
1551                linvel * linvel,
1552                0.0,
1553                1.0,
1554                &pose,
1555                dt,
1556            );
1557        }
1558
1559        activation
1560    }
1561
1562    #[test]
1563    fn test_sleep_allows_pinned_body_with_residual_velocity() {
1564        let dt = 1.0 / 60.0;
1565        let threshold = RigidBodyActivation::default_normalized_linear_threshold();
1566        let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1567
1568        // A still body sleeps even while its velocity reads several times the threshold:
1569        // that residual is the solver cancelling itself, not motion.
1570        assert!(pinned_with_velocity(threshold * 5.0, steps, dt).is_eligible_for_sleep());
1571    }
1572
1573    #[test]
1574    fn test_sleep_gates_position_corrections() {
1575        let dt = 1.0 / 60.0;
1576        // The per-step displacement the sleep metric tolerates: the threshold, halved.
1577        let budget = 2.0 * RigidBodyActivation::default_normalized_linear_threshold() * dt;
1578        let steps = (10.0 * RigidBodyActivation::default_time_until_sleep() / dt) as usize;
1579
1580        // Creeping faster than the budget blocks sleep, however small the velocities are.
1581        assert!(!creep(budget * 1.5, steps, dt).is_eligible_for_sleep());
1582
1583        // Creeping below it doesn't, no matter how long the body has been still.
1584        assert!(creep(budget * 0.5, steps, dt).is_eligible_for_sleep());
1585        assert!(creep(0.0, steps, dt).is_eligible_for_sleep());
1586    }
1587}