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

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