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}