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