avian3d/dynamics/rigid_body/mass_properties/components/
mod.rs

1//! Mass property components.
2
3use crate::prelude::*;
4use bevy::{
5    ecs::{component::HookContext, world::DeferredWorld},
6    prelude::*,
7};
8#[cfg(feature = "3d")]
9use bevy_heavy::AngularInertiaTensor;
10use derive_more::From;
11
12mod collider;
13pub use collider::*;
14
15mod computed;
16pub use computed::*;
17
18/// An error returned for an invalid mass.
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub enum MassError {
21    /// The mass is negative.
22    Negative,
23    /// The mass is NaN.
24    NaN,
25}
26
27/// The [mass] of an entity, representing resistance to linear acceleration.
28/// A higher mass requires more force for the same acceleration.
29///
30/// If [`Mass`] is not present, but the entity has a [`Collider`], its [`ColliderMassProperties`]
31/// computed based on the shape and [`ColliderDensity`] will be used instead.
32///
33/// The [`Mass`] component does *not* take into account the masses of child entities, and it is never modified
34/// by the engine. The total mass of a dynamic [rigid body] that *does* consider child entities and colliders
35/// is stored in the [`ComputedMass`] component. It is updated automatically when mass properties are changed,
36/// or when colliders are added or removed.
37///
38/// A total mass of zero is a special case, and is interpreted as infinite mass, meaning the rigid body
39/// will not be affected by any forces.
40///
41/// [mass]: https://en.wikipedia.org/wiki/Mass
42/// [rigid body]: RigidBody
43///
44/// # Usage
45///
46/// The [`Mass`] component can be used to define the mass of a [rigid body] entity or its descendants:
47///
48/// ```
49#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
50#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
51/// # use bevy::prelude::*;
52/// #
53/// # fn setup(mut commands: Commands) {
54/// commands.spawn((
55///     RigidBody::Dynamic,
56///     Collider::capsule(0.5, 1.5),
57///     Mass(5.0),
58/// ));
59/// # }
60/// ```
61///
62/// If no [`Mass`] is present, the [`ComputedMass`] will be computed from the collider
63/// based on its shape and [`ColliderDensity`].
64///
65/// ```
66#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
67#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
68/// # use bevy::prelude::*;
69/// #
70/// # fn setup(mut commands: Commands) {
71/// // Note: `ColliderDensity` is optional, and defaults to `1.0` if not present.
72/// commands.spawn((
73///     RigidBody::Dynamic,
74///     Collider::capsule(0.5, 1.5),
75///     ColliderDensity(2.0),
76/// ));
77/// # }
78/// ```
79///
80/// If the rigid body has child colliders, their masses will be added to the total [`ComputedMass`].
81///
82/// ```
83#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
84#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
85/// # use bevy::prelude::*;
86/// #
87/// # fn setup(mut commands: Commands) {
88/// // Total mass: 10.0 + 5.0 = 15.0
89/// commands.spawn((
90///     RigidBody::Dynamic,
91///     Collider::capsule(0.5, 1.5),
92///     Mass(10.0),
93/// ))
94#[cfg_attr(
95    feature = "2d",
96    doc = ".with_child((Collider::circle(1.0), Mass(5.0)));"
97)]
98#[cfg_attr(
99    feature = "3d",
100    doc = ".with_child((Collider::sphere(1.0), Mass(5.0)));"
101)]
102/// # }
103/// ```
104///
105/// To prevent masses of child entities from contributing to the total [`ComputedMass`],
106/// add the [`NoAutoMass`] component. This can be useful when full control over mass is desired.
107///
108/// ```
109#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
110#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
111/// # use bevy::prelude::*;
112/// #
113/// # fn setup(mut commands: Commands) {
114/// // Total mass: 10.0
115/// commands.spawn((
116///     RigidBody::Dynamic,
117///     Collider::capsule(0.5, 1.5),
118///     Mass(10.0),
119///     NoAutoMass,
120/// ))
121#[cfg_attr(
122    feature = "2d",
123    doc = ".with_child((Collider::circle(1.0), Mass(5.0)));"
124)]
125#[cfg_attr(
126    feature = "3d",
127    doc = ".with_child((Collider::sphere(1.0), Mass(5.0)));"
128)]
129/// # }
130/// ```
131///
132/// # Mass Updates
133///
134/// The [`Mass`] component is never modified by the engine, so you can safely update it at any time.
135///
136/// The total [`ComputedMass`] is updated automatically whenever the mass properties of a [rigid body]
137/// or its descendants change, or when colliders are added or removed. This update is triggered by adding
138/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
139/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
140///
141/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
142/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
143///
144/// # Related Types
145///
146/// - [`ComputedMass`] stores the total mass of a dynamic [rigid body] that considers child entities and colliders.
147/// - [`NoAutoMass`] disables masses of child entities being taken into account for the [`ComputedMass`].
148/// - [`AngularInertia`] is the rotational equivalent of mass, representing resistance to angular acceleration.
149/// - [`CenterOfMass`] is the local point where the mass is concentrated. Applying forces at this point produces no torque.
150/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
151/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
152///
153/// [`SystemParam`]: bevy::ecs::system::SystemParam
154#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
155#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
156#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
157#[reflect(Debug, Component, Default, PartialEq)]
158pub struct Mass(pub f32);
159
160impl Mass {
161    /// A mass of `0.0`.
162    pub const ZERO: Self = Self(0.0);
163
164    /// Computes the [`Mass`] of the given shape using the given density.
165    ///
166    /// ```
167    #[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
168    #[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
169    /// # use bevy::prelude::*;
170    /// #
171    /// // Compute the mass from a collider with a density of `2.0`.
172    #[cfg_attr(
173        feature = "2d",
174        doc = "let mass = Mass::from_shape(&Collider::circle(1.0), 2.0);"
175    )]
176    #[cfg_attr(
177        feature = "3d",
178        doc = "let mass = Mass::from_shape(&Collider::sphere(1.0), 2.0);"
179    )]
180    ///
181    /// // Bevy's primitive shapes can also be used.
182    #[cfg_attr(
183        feature = "2d",
184        doc = "let mass = Mass::from_shape(&Circle::new(1.0), 2.0);"
185    )]
186    #[cfg_attr(
187        feature = "3d",
188        doc = "let mass = Mass::from_shape(&Sphere::new(1.0), 2.0);"
189    )]
190    /// ```
191    #[inline]
192    pub fn from_shape<T: ComputeMassProperties>(shape: &T, density: f32) -> Self {
193        Self(shape.mass(density))
194    }
195}
196
197// TODO: Add errors for asymmetric and non-positive definite matrices in 3D.
198/// An error returned for an invalid angular inertia.
199#[derive(Clone, Copy, Debug, PartialEq)]
200pub enum AngularInertiaError {
201    /// The angular inertia is negative.
202    Negative,
203    /// The angular inertia is NaN.
204    NaN,
205}
206
207/// The [angular inertia] of an entity, representing resistance to angular acceleration.
208/// A higher angular inertia requires more torque for the same acceleration.
209///
210/// If [`AngularInertia`] is not present, but the entity has a [`Collider`], its [`ColliderMassProperties`]
211/// computed based on the shape and will be used instead. Angular inertia scales with mass,
212/// so a higher [`Mass`] or [`ColliderDensity`] will result in higher inertia.
213///
214/// The [`AngularInertia`] component does *not* take into account the inertia of child entities, and it is never modified
215/// by the engine. The total angular inertia of a dynamic [rigid body] that *does* consider child entities and colliders
216/// is stored in the [`ComputedAngularInertia`] component. It is updated automatically when mass properties are changed,
217/// or when colliders are added or removed.
218///
219/// A total angular inertia of zero is a special case, and is interpreted as infinite inertia, meaning the rigid body
220/// will not be affected by any torque.
221///
222/// [angular inertia]: https://en.wikipedia.org/wiki/Moment_of_inertia
223/// [rigid body]: RigidBody
224///
225/// # Usage
226///
227/// The [`AngularInertia`] component can be used to define the angular inertia of a [rigid body]
228/// entity or its descendants:
229///
230/// ```
231/// # use avian2d::prelude::*;
232/// # use bevy::prelude::*;
233/// #
234/// # fn setup(mut commands: Commands) {
235/// commands.spawn((
236///     RigidBody::Dynamic,
237///     Collider::capsule(0.5, 1.5),
238///     AngularInertia(2.0),
239/// ));
240/// # }
241/// ```
242///
243/// If no [`AngularInertia`] is present, the [`ComputedAngularInertia`] will be computed from the collider
244/// based on its mass and shape.
245///
246/// ```
247/// # use avian2d::prelude::*;
248/// # use bevy::prelude::*;
249/// #
250/// # fn setup(mut commands: Commands) {
251/// // Note: `ColliderDensity` is optional, and defaults to `1.0` if not present.
252/// commands.spawn((
253///     RigidBody::Dynamic,
254///     Collider::capsule(0.5, 1.5),
255///     ColliderDensity(2.0),
256/// ));
257/// # }
258/// ```
259///
260/// If the rigid body has child colliders, their angular inertia will be added to the total [`ComputedAngularInertia`].
261///
262/// ```
263/// # use avian2d::prelude::*;
264/// # use bevy::prelude::*;
265/// #
266/// # fn setup(mut commands: Commands) {
267/// // Total angular inertia: 3.0 + 2.0 = 5.0
268/// commands.spawn((
269///     RigidBody::Dynamic,
270///     Collider::capsule(0.5, 1.5),
271///     AngularInertia(3.0),
272/// ))
273/// .with_child((Collider::circle(1.0), AngularInertia(2.0)));
274/// # }
275/// ```
276///
277/// To prevent angular inertia of child entities from contributing to the total [`ComputedAngularInertia`],
278/// add the [`NoAutoAngularInertia`] component. This can be useful when full control over inertia is desired.
279///
280/// ```
281/// # use avian2d::prelude::*;
282/// # use bevy::prelude::*;
283/// #
284/// # fn setup(mut commands: Commands) {
285/// // Total angular inertia: 3.0
286/// commands.spawn((
287///     RigidBody::Dynamic,
288///     Collider::capsule(0.5, 1.5),
289///     AngularInertia(3.0),
290///     NoAutoAngularInertia,
291/// ))
292/// .with_child((Collider::circle(1.0), AngularInertia(2.0)));
293/// # }
294/// ```
295///
296/// # Angular Inertia Updates
297///
298/// The [`AngularInertia`] component is never modified by the engine, so you can safely update it at any time.
299///
300/// The total [`ComputedAngularInertia`] is updated automatically whenever the mass properties of a [rigid body]
301/// or its descendants change, or when colliders are added or removed. This update is triggered by adding
302/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
303/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
304///
305/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
306/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
307///
308/// # Related Types
309///
310/// - [`ComputedAngularInertia`] stores the total angular inertia of a dynamic [rigid body] that considers child entities and colliders.
311/// - [`NoAutoAngularInertia`] disables the mass properties of child entities being taken into account for the [`ComputedAngularInertia`].
312/// - [`Mass`] is the linear equivalent of angular inertia, representing resistance to linear acceleration.
313/// - [`CenterOfMass`] is the local point where the mass is concentrated. Applying forces at this point produces no torque.
314/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
315/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
316///
317/// [`SystemParam`]: bevy::ecs::system::SystemParam
318#[cfg(feature = "2d")]
319#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
320#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
321#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
322#[reflect(Debug, Component, Default, PartialEq)]
323#[doc(alias = "MomentOfInertia")]
324pub struct AngularInertia(pub f32);
325
326#[cfg(feature = "2d")]
327impl AngularInertia {
328    /// An angular inertia of `0.0`.
329    pub const ZERO: Self = Self(0.0);
330
331    /// Computes the [`AngularInertia`] of the given shape using the given mass.
332    ///
333    /// ```
334    /// # use avian2d::prelude::*;
335    /// # use bevy::prelude::*;
336    /// #
337    /// // Compute the angular inertia from a collider with a mass of `2.0`.
338    /// let inertia = AngularInertia::from_shape(&Collider::circle(1.0), 2.0);
339    ///
340    /// // Bevy's primitive shapes can also be used.
341    /// let inertia = AngularInertia::from_shape(&Circle::new(1.0), 2.0);
342    /// ```
343    #[inline]
344    pub fn from_shape<T: ComputeMassProperties>(shape: &T, mass: f32) -> Self {
345        Self(shape.angular_inertia(mass))
346    }
347
348    /// Computes the angular inertia shifted by the given offset, taking into account the given mass.
349    #[inline]
350    pub fn shifted(&self, mass: f32, offset: Vec2) -> f32 {
351        if mass > 0.0 && mass.is_finite() && offset != Vec2::ZERO {
352            self.0 + offset.length_squared() * mass
353        } else {
354            self.0
355        }
356    }
357}
358
359/// The local [angular inertia] of an entity, representing resistance to angular acceleration.
360/// A higher angular inertia requires more torque for the same acceleration.
361///
362/// If [`AngularInertia`] is not present, but the entity has a [`Collider`], its [`ColliderMassProperties`]
363/// computed based on the shape and [mass] will be used instead. Angular inertia scales with mass,
364/// so a higher [`Mass`] or [`ColliderDensity`] will result in higher inertia.
365///
366/// The [`AngularInertia`] component does *not* take into account the inertia of child entities, and it is never modified
367/// by the engine. The total angular inertia of a dynamic [rigid body] that *does* consider child entities and colliders
368/// is stored in the [`ComputedAngularInertia`] component. It is updated automatically when mass properties are changed,
369/// or when colliders are added or removed.
370///
371/// A total angular inertia of zero is a special case, and is interpreted as infinite inertia, meaning the rigid body
372/// will not be affected by any torques.
373///
374/// See the [module-level documentation] for more general information on angular inertia.
375///
376/// [angular inertia]: https://en.wikipedia.org/wiki/Moment_of_inertia
377/// [rigid body]: RigidBody
378/// [mass]: super#mass
379/// [module-level documentation]: super#angular-inertia
380///
381/// # Usage
382///
383/// [`AngularInertia`] is defined by the moment of inertia along the *principal axes* (a [`Vec3`])
384/// defined by the *local inertial frame* (a [`Quat`]). Most entities will have a local inertial frame
385/// of [`Quat::IDENTITY`], which means that the principal axes are aligned with the entity's local axes.
386///
387/// ```
388/// # use avian3d::prelude::*;
389/// # use bevy::prelude::*;
390/// #
391/// # #[cfg(feature = "f32")]
392/// # fn setup(mut commands: Commands) {
393/// // Principal angular inertia: `2.0` for the local X and Z axes, and `5.0` for the Y axis.
394/// let inertia1 = AngularInertia::new(Vec3::new(2.0, 5.0, 2.0));
395///
396/// // A local inertial frame rotated by 90 degrees about the X axis.
397/// let inertia2 = AngularInertia::new_with_local_frame(Vec3::new(2.0, 5.0, 2.0), Quat::from_rotation_x(PI / 2.0));
398/// # }
399/// # #[cfg(not(feature = "f32"))]
400/// # fn main() {}
401///
402/// ```
403///
404/// [`AngularInertia`] can also be created from a symmetric 3x3 matrix known as the *[angular inertia tensor]*.
405/// It summarizes all moments of inertia of an object in a single matrix, and can be used to perform
406/// computations with angular inertia, but is often not constructed manually.
407///
408/// [angular inertia tensor]: AngularInertiaTensor
409///
410/// ```
411/// # use avian3d::prelude::*;
412/// # use bevy::prelude::*;
413/// #
414/// # #[cfg(feature = "f32")]
415/// # fn setup(mut commands: Commands) {
416/// // For simplicity, we use the same principal angular inertia as before, with an identity local frame.
417/// let inertia1 = AngularInertia::from_tensor(Mat3::from_diagonal(Vec3::new(2.0, 5.0, 2.0)));
418///
419/// // The angular inertia tensor can be retrieved back from the principal angular inertia.
420/// let tensor = inertia1.tensor();
421/// # }
422/// # #[cfg(not(feature = "f32"))]
423/// # fn main() {}
424/// ```
425///
426/// The [`AngularInertia`] component can be used to define the angular inertia of a [rigid body]
427/// entity or its descendants:
428///
429/// ```
430/// # use avian3d::prelude::*;
431/// # use bevy::prelude::*;
432/// #
433/// # #[cfg(feature = "f32")]
434/// # fn setup(mut commands: Commands) {
435/// commands.spawn((
436///     RigidBody::Dynamic,
437///     Collider::capsule(0.5, 1.5),
438///     AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
439/// ));
440/// # }
441/// # #[cfg(not(feature = "f32"))]
442/// # fn main() {}
443/// ```
444///
445/// If no [`AngularInertia`] is present, the [`ComputedAngularInertia`] will be computed from the collider
446/// based on its mass and shape.
447///
448/// ```
449/// # use avian3d::prelude::*;
450/// # use bevy::prelude::*;
451/// #
452/// # fn setup(mut commands: Commands) {
453/// // Note: `ColliderDensity` is optional, and defaults to `1.0` if not present.
454/// commands.spawn((
455///     RigidBody::Dynamic,
456///     Collider::capsule(0.5, 1.5),
457///     ColliderDensity(2.0),
458/// ));
459/// # }
460/// ```
461///
462/// If the rigid body has child colliders, their angular inertia will be added to the total [`ComputedAngularInertia`].
463/// Here both entities are at the origin, but offsets relative to the center of mass would also impact the total inertia.
464///
465/// ```
466/// # use avian3d::prelude::*;
467/// # use bevy::prelude::*;
468/// #
469/// # #[cfg(feature = "f32")]
470/// # fn setup(mut commands: Commands) {
471/// // Total angular inertia: [2.0, 5.0, 2.0] + [1.0, 2.0, 1.0] = [3.0, 7.0, 3.0]
472/// commands.spawn((
473///     RigidBody::Dynamic,
474///     Collider::capsule(0.5, 1.5),
475///     AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
476/// ))
477/// .with_child((Collider::sphere(1.0), AngularInertia::new(Vec3::new(1.0, 2.0, 1.0))));
478/// # }
479/// # #[cfg(not(feature = "f32"))]
480/// # fn main() {}
481/// ```
482///
483/// To prevent angular inertia of child entities from contributing to the total [`ComputedAngularInertia`],
484/// add the [`NoAutoAngularInertia`] component. This can be useful when full control over inertia is desired.
485///
486/// ```
487/// # use avian3d::prelude::*;
488/// # use bevy::prelude::*;
489/// #
490/// # #[cfg(feature = "f32")]
491/// # fn setup(mut commands: Commands) {
492/// // Total angular inertia: [2.0, 5.0, 2.0]
493/// commands.spawn((
494///     RigidBody::Dynamic,
495///     Collider::capsule(0.5, 1.5),
496///     AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
497///     NoAutoAngularInertia,
498/// ))
499/// .with_child((Collider::sphere(1.0), AngularInertia::new(Vec3::new(1.0, 2.0, 1.0))));
500/// # }
501/// # #[cfg(not(feature = "f32"))]
502/// # fn main() {}
503/// ```
504///
505/// # Angular Inertia Updates
506///
507/// The [`AngularInertia`] component is never modified by the engine, so you can safely update it at any time.
508///
509/// The total [`ComputedAngularInertia`] is updated automatically whenever the mass properties of a [rigid body]
510/// or its descendants change, or when colliders are added or removed. This update is triggered by adding
511/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
512/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
513///
514/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
515/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
516///
517/// # Related Types
518///
519/// - [`ComputedAngularInertia`] stores the total angular inertia of a dynamic [rigid body] that considers child entities and colliders.
520/// - [`NoAutoAngularInertia`] disables the mass properties of child entities being taken into account for the [`ComputedAngularInertia`].
521/// - [`AngularInertiaTensor`] is the symmetric 3x3 matrix representation of angular inertia.
522/// - [`Mass`] is the linear equivalent of angular inertia, representing resistance to linear acceleration.
523/// - [`CenterOfMass`] is the local point where the mass is concentrated. Applying forces at this point produces no torque.
524/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
525/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
526///
527/// [`SystemParam`]: bevy::ecs::system::SystemParam
528#[cfg(feature = "3d")]
529#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
530#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
531#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
532#[reflect(Debug, Component, PartialEq)]
533#[doc(alias = "MomentOfInertia")]
534pub struct AngularInertia {
535    /// The principal angular inertia, representing resistance to angular acceleration
536    /// about the local coordinate axes defined by the `local_frame`.
537    pub principal: Vec3,
538    /// The orientation of the local inertial frame.
539    pub local_frame: Quat,
540}
541
542#[cfg(feature = "3d")]
543impl AngularInertia {
544    /// An angular inertia of `0.0` for all axes, with an identity local frame.
545    pub const ZERO: Self = Self {
546        principal: Vec3::ZERO,
547        local_frame: Quat::IDENTITY,
548    };
549
550    /// Creates a new [`AngularInertia`] from the given principal angular inertia.
551    ///
552    /// The principal angular inertia represents resistance to angular acceleration
553    /// about the local coordinate axes.
554    ///
555    /// To specify the orientation of the local inertial frame, consider using [`AngularInertia::new_with_local_frame`].
556    ///
557    /// # Panics
558    ///
559    /// Panics if any component of the principal angular inertia is negative or NaN when `debug_assertions` are enabled.
560    #[inline]
561    #[doc(alias = "from_principal_angular_inertia")]
562    pub fn new(principal_angular_inertia: Vec3) -> Self {
563        debug_assert!(
564            principal_angular_inertia.cmpge(Vec3::ZERO).all()
565                && !principal_angular_inertia.is_nan(),
566            "principal angular inertia must be positive or zero for all axes"
567        );
568
569        Self {
570            principal: principal_angular_inertia,
571            local_frame: Quat::IDENTITY,
572        }
573    }
574
575    /// Tries to create a new [`AngularInertia`] from the given principal angular inertia.
576    ///
577    /// The principal angular inertia represents resistance to angular acceleration
578    /// about the local coordinate axes. To specify the orientation of the local inertial frame,
579    /// consider using [`AngularInertia::try_new_with_local_frame`].
580    ///
581    /// # Errors
582    ///
583    /// Returns [`Err(AngularInertiaError)`](AngularInertiaError) if any component of the principal angular inertia is negative or NaN.
584    #[inline]
585    pub fn try_new(principal_angular_inertia: Vec3) -> Result<Self, AngularInertiaError> {
586        if principal_angular_inertia.is_nan() {
587            Err(AngularInertiaError::NaN)
588        } else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
589            Err(AngularInertiaError::Negative)
590        } else {
591            Ok(Self {
592                principal: principal_angular_inertia,
593                local_frame: Quat::IDENTITY,
594            })
595        }
596    }
597
598    /// Creates a new [`AngularInertia`] from the given principal angular inertia
599    /// and the orientation of the local inertial frame.
600    ///
601    /// The principal angular inertia represents resistance to angular acceleration
602    /// about the local coordinate axes defined by the given `local_frame`.
603    ///
604    /// # Panics
605    ///
606    /// Panics if any component of the principal angular inertia is negative or NaN when `debug_assertions` are enabled.
607    #[inline]
608    #[doc(alias = "from_principal_angular_inertia_with_local_frame")]
609    pub fn new_with_local_frame(principal_angular_inertia: Vec3, local_frame: Quat) -> Self {
610        debug_assert!(
611            principal_angular_inertia.cmpge(Vec3::ZERO).all()
612                && !principal_angular_inertia.is_nan(),
613            "principal angular inertia must be positive or zero for all axes"
614        );
615
616        Self {
617            principal: principal_angular_inertia,
618            local_frame,
619        }
620    }
621
622    /// Tries to create a new [`AngularInertia`] from the given principal angular inertia
623    /// and the orientation of the local inertial frame.
624    ///
625    /// The principal angular inertia represents resistance to angular acceleration
626    /// about the local coordinate axes defined by the given `local_frame`.
627    ///
628    /// # Errors
629    ///
630    /// Returns [`Err(AngularInertiaError)`](AngularInertiaError) if any component of the principal angular inertia is negative or NaN.
631    #[inline]
632    pub fn try_new_with_local_frame(
633        principal_angular_inertia: Vec3,
634        local_frame: Quat,
635    ) -> Result<Self, AngularInertiaError> {
636        if principal_angular_inertia.is_nan() {
637            Err(AngularInertiaError::NaN)
638        } else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
639            Err(AngularInertiaError::Negative)
640        } else {
641            Ok(Self {
642                principal: principal_angular_inertia,
643                local_frame,
644            })
645        }
646    }
647
648    /// Creates a new [`AngularInertia`] from the given [angular inertia tensor].
649    ///
650    /// The tensor should be symmetric and positive definite.
651    ///
652    /// [angular inertia tensor]: AngularInertiaTensor
653    #[inline]
654    #[doc(alias = "from_mat3")]
655    pub fn from_tensor(tensor: impl Into<AngularInertiaTensor>) -> Self {
656        let tensor = tensor.into();
657        let (principal, local_frame) = tensor.principal_angular_inertia_with_local_frame();
658
659        Self {
660            principal,
661            local_frame,
662        }
663    }
664
665    /// Computes the [`AngularInertia`] of the given shape using the given mass.
666    ///
667    /// ```
668    /// # use avian3d::prelude::*;
669    /// # use bevy::prelude::*;
670    /// #
671    /// // Compute the angular inertia from collider with a mass of `2.0`.
672    /// let inertia = AngularInertia::from_shape(&Collider::sphere(1.0), 2.0);
673    ///
674    /// // Bevy's primitive shapes can also be used.
675    /// let inertia = AngularInertia::from_shape(&Sphere::new(1.0), 2.0);
676    /// ```
677    #[inline]
678    pub fn from_shape<T: ComputeMassProperties>(shape: &T, mass: f32) -> Self {
679        let principal = shape.principal_angular_inertia(mass);
680        let local_frame = shape.local_inertial_frame();
681        Self::new_with_local_frame(principal, local_frame)
682    }
683
684    /// Returns the [`AngularInertiaTensor`] represented by this principal [`AngularInertia`]
685    /// and local inertial frame.
686    #[inline]
687    pub fn tensor(self) -> AngularInertiaTensor {
688        AngularInertiaTensor::new_with_local_frame(self.principal, self.local_frame)
689    }
690
691    /// Returns `true` if the principal angular inertia and inertial local frame are neither infinite nor NaN.
692    #[inline]
693    pub fn is_finite(self) -> bool {
694        self.principal.is_finite() && self.local_frame.is_finite()
695    }
696
697    /// Returns `true` if the principal angular inertia or inertial local frame is NaN.
698    #[inline]
699    pub fn is_nan(self) -> bool {
700        self.principal.is_nan() || self.local_frame.is_nan()
701    }
702}
703
704#[cfg(feature = "3d")]
705impl From<Mat3> for AngularInertia {
706    fn from(tensor: Mat3) -> Self {
707        Self::from_tensor(tensor)
708    }
709}
710
711#[cfg(feature = "3d")]
712impl From<AngularInertiaTensor> for AngularInertia {
713    fn from(tensor: AngularInertiaTensor) -> Self {
714        Self::from_tensor(tensor)
715    }
716}
717
718#[cfg(feature = "3d")]
719impl From<AngularInertia> for AngularInertiaTensor {
720    fn from(inertia: AngularInertia) -> Self {
721        inertia.tensor()
722    }
723}
724
725/// The local [center of mass] of an entity, representing the average position of mass
726/// in the object. Applying forces at this point produces no torque.
727///
728/// If [`CenterOfMass`] is not present, but the entity has a [`Collider`], its [`ColliderMassProperties`]
729/// computed based on the shape will be used instead.
730///
731/// The [`CenterOfMass`] component does *not* take into account child entities, and it is never modified
732/// by the engine. The total center of mass of a dynamic [rigid body] that *does* consider child entities and colliders
733/// is stored in the [`ComputedCenterOfMass`] component. It is updated automatically when mass properties are changed,
734/// or when colliders are added or removed.
735///
736/// [center of mass]: https://en.wikipedia.org/wiki/Center_of_mass
737/// [rigid body]: RigidBody
738///
739/// # Usage
740///
741/// The [`CenterOfMass`] component can be used to define the center of mass of a [rigid body] entity or its descendants:
742///
743/// ```
744#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
745#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
746/// # use bevy::prelude::*;
747/// #
748/// # fn setup(mut commands: Commands) {
749/// // An offset of `-0.5` along the Y axis.
750/// commands.spawn((
751///     RigidBody::Dynamic,
752///     Collider::capsule(0.5, 1.5),
753#[cfg_attr(feature = "2d", doc = "    CenterOfMass::new(0.0, -0.5),")]
754#[cfg_attr(feature = "3d", doc = "    CenterOfMass::new(0.0, -0.5, 0.0),")]
755/// ));
756/// # }
757/// ```
758///
759/// If no [`CenterOfMass`] is present, the [`ComputedCenterOfMass`] will be computed from the collider
760/// based on its shape.
761///
762/// ```
763#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
764#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
765/// # use bevy::prelude::*;
766/// #
767/// # fn setup(mut commands: Commands) {
768/// // For a capsule, the center of mass is at the local origin.
769/// commands.spawn((RigidBody::Dynamic, Collider::capsule(0.5, 1.5)));
770/// # }
771/// ```
772///
773/// If the rigid body has child colliders, they will contribute to the total [`ComputedCenterOfMass`]
774/// based on weighted average of their global centers of mass.
775///
776/// ```
777#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
778#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
779/// # use bevy::prelude::*;
780/// #
781/// # fn setup(mut commands: Commands) {
782#[cfg_attr(
783    feature = "2d",
784    doc = "// Total center of mass: (10.0 * [0.0, -0.5] + 5.0 * [0.0, 4.0]) / (10.0 + 5.0) = [0.0, 1.0]"
785)]
786#[cfg_attr(
787    feature = "3d",
788    doc = "// Total center of mass: (10.0 * [0.0, -0.5, 0.0] + 5.0 * [0.0, 4.0, 0.0]) / (10.0 + 5.0) = [0.0, 1.0, 0.0]"
789)]
790/// commands.spawn((
791///     RigidBody::Dynamic,
792///     Collider::capsule(0.5, 1.5),
793///     Mass(10.0),
794#[cfg_attr(feature = "2d", doc = "    CenterOfMass::new(0.0, -0.5),")]
795#[cfg_attr(feature = "3d", doc = "    CenterOfMass::new(0.0, -0.5, 0.0),")]
796///     Transform::default(),
797/// ))
798/// .with_child((
799#[cfg_attr(feature = "2d", doc = "    Collider::circle(1.0),")]
800#[cfg_attr(feature = "3d", doc = "    Collider::sphere(1.0),")]
801///     Mass(5.0),
802///     Transform::from_xyz(0.0, 4.0, 0.0),
803/// ));
804/// # }
805/// ```
806///
807/// To prevent the centers of mass of child entities from contributing to the total [`ComputedCenterOfMass`],
808/// add the [`NoAutoCenterOfMass`], component. This can be useful when full control over the center of mass is desired.
809///
810/// ```
811#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
812#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
813/// # use bevy::prelude::*;
814/// #
815/// # fn setup(mut commands: Commands) {
816#[cfg_attr(feature = "2d", doc = "// Total center of mass: [0.0, -0.5]")]
817#[cfg_attr(feature = "3d", doc = "// Total center of mass: [0.0, -0.5, 0.0]")]
818/// commands.spawn((
819///     RigidBody::Dynamic,
820///     Collider::capsule(0.5, 1.5),
821#[cfg_attr(feature = "2d", doc = "    CenterOfMass::new(0.0, -0.5),")]
822#[cfg_attr(feature = "3d", doc = "    CenterOfMass::new(0.0, -0.5, 0.0),")]
823///     Transform::default(),
824/// ))
825/// .with_child((
826#[cfg_attr(feature = "2d", doc = "    Collider::circle(1.0),")]
827#[cfg_attr(feature = "3d", doc = "    Collider::sphere(1.0),")]
828///     Mass(5.0),
829///     Transform::from_xyz(0.0, 4.0, 0.0),
830/// ));
831/// # }
832/// ```
833///
834/// # Center of Mass Updates
835///
836/// The [`CenterOfMass`] component is never modified by the engine, so you can safely update it at any time.
837///
838/// The total [`ComputedCenterOfMass`] is updated automatically whenever the mass properties of a [rigid body]
839/// or its descendants change, or when colliders are added, removed, or transformed. This update is triggered by adding
840/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
841/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
842///
843/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
844/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
845///
846/// # Related Types
847///
848/// - [`ComputedCenterOfMass`] stores the total center of mass of a dynamic [rigid body] that considers child entities and colliders.
849/// - [`NoAutoCenterOfMass`] disables the centers of mass of child entities being taken into account for the [`ComputedCenterOfMass`].
850/// - [`Mass`] represents resistance to linear acceleration.
851/// - [`AngularInertia`] is the rotational equivalent of mass, representing resistance to angular acceleration.
852/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
853/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
854///
855/// [`SystemParam`]: bevy::ecs::system::SystemParam
856#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
857#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
858#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
859#[reflect(Debug, Component, Default, PartialEq)]
860pub struct CenterOfMass(pub VectorF32);
861
862impl CenterOfMass {
863    /// A center of mass set at the local origin.
864    pub const ZERO: Self = Self(VectorF32::ZERO);
865
866    /// Creates a new [`CenterOfMass`] at the given local position.
867    #[inline]
868    #[cfg(feature = "2d")]
869    pub const fn new(x: f32, y: f32) -> Self {
870        Self(Vec2::new(x, y))
871    }
872
873    /// Creates a new [`CenterOfMass`] at the given local position.
874    #[inline]
875    #[cfg(feature = "3d")]
876    pub const fn new(x: f32, y: f32, z: f32) -> Self {
877        Self(Vec3::new(x, y, z))
878    }
879
880    /// Computes the [`CenterOfMass`] of the given shape.
881    ///
882    /// ```
883    #[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
884    #[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
885    /// # use bevy::prelude::*;
886    /// #
887    /// // Compute the center of mass from a collider.
888    #[cfg_attr(
889        feature = "2d",
890        doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::circle(1.0));"
891    )]
892    #[cfg_attr(
893        feature = "3d",
894        doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::sphere(1.0));"
895    )]
896    ///
897    /// // Bevy's primitive shapes can also be used.
898    #[cfg_attr(
899        feature = "2d",
900        doc = "let center_of_mass = CenterOfMass::from_shape(&Circle::new(1.0));"
901    )]
902    #[cfg_attr(
903        feature = "3d",
904        doc = "let center_of_mass = CenterOfMass::from_shape(&Sphere::new(1.0));"
905    )]
906    /// ```
907    #[inline]
908    pub fn from_shape<T: ComputeMassProperties>(shape: &T) -> Self {
909        Self(shape.center_of_mass())
910    }
911}
912
913/// A marker component that prevents descendants or attached colliders
914/// from contributing to the total [`ComputedMass`] of a [rigid body].
915///
916/// Only the [`Mass`] component of the rigid body entity itself will be considered.
917/// This is useful when full control over mass is desired.
918///
919/// [rigid body]: RigidBody
920#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
921#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
922#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
923#[reflect(Debug, Component, Default, PartialEq)]
924#[require(RecomputeMassProperties)]
925#[component(on_remove = on_remove_no_auto_mass_property)]
926pub struct NoAutoMass;
927
928/// A marker component that prevents descendants or attached colliders
929/// from contributing to the total [`ComputedAngularInertia`] of a [rigid body].
930///
931/// Only the [`AngularInertia`] component of the rigid body entity itself will be considered.
932/// This is useful when full control over inertia is desired.
933///
934/// [rigid body]: RigidBody
935#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
936#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
937#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
938#[reflect(Debug, Component, Default, PartialEq)]
939#[require(RecomputeMassProperties)]
940#[component(on_remove = on_remove_no_auto_mass_property)]
941pub struct NoAutoAngularInertia;
942
943/// A marker component that prevents descendants or attached colliders
944/// from contributing to the total [`ComputedCenterOfMass`] of a [rigid body].
945///
946/// Only the [`CenterOfMass`] component of the rigid body entity itself will be considered.
947/// This is useful when full control over the center of mass is desired.
948///
949/// [rigid body]: RigidBody
950#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
951#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
952#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
953#[reflect(Debug, Component, Default, PartialEq)]
954#[require(RecomputeMassProperties)]
955#[component(on_remove = on_remove_no_auto_mass_property)]
956pub struct NoAutoCenterOfMass;
957
958/// Triggers the recomputation of mass properties for rigid bodies when automatic computation is re-enabled.
959fn on_remove_no_auto_mass_property(mut world: DeferredWorld, ctx: HookContext) {
960    if let Ok(mut entity_commands) = world.commands().get_entity(ctx.entity) {
961        entity_commands.try_insert(RecomputeMassProperties);
962    }
963}
964
965/// A marker component that forces the recomputation of [`ComputedMass`], [`ComputedAngularInertia`]
966/// and [`ComputedCenterOfMass`] for a [rigid body].
967///
968/// This is added automatically when the mass properties of a [rigid body] change, or when colliders are added or removed,
969/// and is removed after the recomputation in [`MassPropertySystems::UpdateComputedMassProperties`].
970///
971/// [rigid body]: RigidBody
972/// [`MassPropertySystems::UpdateComputedMassProperties`]: super::MassPropertySystems::UpdateComputedMassProperties
973#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
974#[component(storage = "SparseSet")]
975pub struct RecomputeMassProperties;
976
977/// A bundle containing [mass properties].
978///
979/// [mass properties]: super
980#[allow(missing_docs)]
981#[derive(Bundle, Debug, Default, Clone, PartialEq)]
982#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
983pub struct MassPropertiesBundle {
984    pub mass: Mass,
985    pub angular_inertia: AngularInertia,
986    pub center_of_mass: CenterOfMass,
987}
988
989impl MassPropertiesBundle {
990    /// Computes the mass properties for a [`Collider`] based on its shape and a given density.
991    ///
992    /// ```
993    #[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
994    #[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
995    /// # use bevy::prelude::*;
996    /// #
997    /// # fn setup(mut commands: Commands) {
998    /// // Compute mass properties from a collider with a density of `2.0`.
999    /// commands.spawn((
1000    ///     RigidBody::Dynamic,
1001    #[cfg_attr(
1002        feature = "2d",
1003        doc = "    MassPropertiesBundle::from_shape(&Collider::circle(0.5), 2.0),"
1004    )]
1005    #[cfg_attr(
1006        feature = "3d",
1007        doc = "    MassPropertiesBundle::from_shape(&Collider::sphere(0.5), 2.0),"
1008    )]
1009    /// ));
1010    ///
1011    /// // Bevy's primitive shapes can also be used.
1012    /// commands.spawn((
1013    ///     RigidBody::Dynamic,
1014    #[cfg_attr(
1015        feature = "2d",
1016        doc = "    MassPropertiesBundle::from_shape(&Circle::new(0.5), 2.0),"
1017    )]
1018    #[cfg_attr(
1019        feature = "3d",
1020        doc = "    MassPropertiesBundle::from_shape(&Sphere::new(0.5), 2.0),"
1021    )]
1022    /// ));
1023    /// # }
1024    /// ```
1025    #[inline]
1026    pub fn from_shape<T: ComputeMassProperties>(shape: &T, density: f32) -> Self {
1027        shape.mass_properties(density).to_bundle()
1028    }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    #[cfg(feature = "3d")]
1034    use super::*;
1035    #[cfg(feature = "3d")]
1036    use approx::assert_relative_eq;
1037
1038    #[test]
1039    #[cfg(feature = "3d")]
1040    fn angular_inertia_creation() {
1041        use bevy_heavy::AngularInertiaTensor;
1042
1043        let angular_inertia = AngularInertia::new(Vec3::new(10.0, 20.0, 30.0));
1044        assert_eq!(angular_inertia.principal, Vec3::new(10.0, 20.0, 30.0));
1045        assert_eq!(angular_inertia.local_frame, Quat::IDENTITY);
1046        assert_relative_eq!(
1047            angular_inertia.tensor(),
1048            AngularInertiaTensor::new(Vec3::new(10.0, 20.0, 30.0))
1049        );
1050    }
1051
1052    #[test]
1053    #[should_panic]
1054    #[cfg(feature = "3d")]
1055    fn negative_angular_inertia_panics() {
1056        AngularInertia::new(Vec3::new(-1.0, 2.0, 3.0));
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "3d")]
1061    fn negative_angular_inertia_error() {
1062        assert_eq!(
1063            AngularInertia::try_new(Vec3::new(-1.0, 2.0, 3.0)),
1064            Err(AngularInertiaError::Negative),
1065            "negative angular inertia should return an error"
1066        );
1067    }
1068
1069    #[test]
1070    #[cfg(feature = "3d")]
1071    fn nan_angular_inertia_error() {
1072        assert_eq!(
1073            AngularInertia::try_new(Vec3::new(f32::NAN, 2.0, 3.0)),
1074            Err(AngularInertiaError::NaN),
1075            "NaN angular inertia should return an error"
1076        );
1077    }
1078}