avian3d/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/// # use core::f32::consts::PI;
393/// #
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///
402/// ```
403///
404/// [`AngularInertia`] can also be created from a symmetric 3x3 matrix known as the *[angular inertia tensor]*.
405/// It summarizes all moments of inertia of an object in a single matrix, and can be used to perform
406/// computations with angular inertia, but is often not constructed manually.
407///
408/// [angular inertia tensor]: AngularInertiaTensor
409///
410/// ```
411/// # use avian3d::prelude::*;
412/// # use bevy::prelude::*;
413/// #
414/// # fn setup(mut commands: Commands) {
415/// // For simplicity, we use the same principal angular inertia as before, with an identity local frame.
416/// let inertia1 = AngularInertia::from_tensor(AngularInertiaTensor::new(Vec3::new(2.0, 5.0, 2.0)));
417///
418/// // The angular inertia tensor can be retrieved back from the principal angular inertia.
419/// let tensor = inertia1.tensor();
420/// # }
421/// ```
422///
423/// The [`AngularInertia`] component can be used to define the angular inertia of a [rigid body]
424/// entity or its descendants:
425///
426/// ```
427/// # use avian3d::prelude::*;
428/// # use bevy::prelude::*;
429/// #
430/// # fn setup(mut commands: Commands) {
431/// commands.spawn((
432/// RigidBody::Dynamic,
433/// Collider::capsule(0.5, 1.5),
434/// AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
435/// ));
436/// # }
437/// ```
438///
439/// If no [`AngularInertia`] is present, the [`ComputedAngularInertia`] will be computed from the collider
440/// based on its mass and shape.
441///
442/// ```
443/// # use avian3d::prelude::*;
444/// # use bevy::prelude::*;
445/// #
446/// # fn setup(mut commands: Commands) {
447/// // Note: `ColliderDensity` is optional, and defaults to `1.0` if not present.
448/// commands.spawn((
449/// RigidBody::Dynamic,
450/// Collider::capsule(0.5, 1.5),
451/// ColliderDensity(2.0),
452/// ));
453/// # }
454/// ```
455///
456/// If the rigid body has child colliders, their angular inertia will be added to the total [`ComputedAngularInertia`].
457/// Here both entities are at the origin, but offsets relative to the center of mass would also impact the total inertia.
458///
459/// ```
460/// # use avian3d::prelude::*;
461/// # use bevy::prelude::*;
462/// #
463/// # fn setup(mut commands: Commands) {
464/// // Total angular inertia: [2.0, 5.0, 2.0] + [1.0, 2.0, 1.0] = [3.0, 7.0, 3.0]
465/// commands.spawn((
466/// RigidBody::Dynamic,
467/// Collider::capsule(0.5, 1.5),
468/// AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
469/// ))
470/// .with_child((Collider::sphere(1.0), AngularInertia::new(Vec3::new(1.0, 2.0, 1.0))));
471/// # }
472/// ```
473///
474/// To prevent angular inertia of child entities from contributing to the total [`ComputedAngularInertia`],
475/// add the [`NoAutoAngularInertia`] component. This can be useful when full control over inertia is desired.
476///
477/// ```
478/// # use avian3d::prelude::*;
479/// # use bevy::prelude::*;
480/// #
481/// # fn setup(mut commands: Commands) {
482/// // Total angular inertia: [2.0, 5.0, 2.0]
483/// commands.spawn((
484/// RigidBody::Dynamic,
485/// Collider::capsule(0.5, 1.5),
486/// AngularInertia::new(Vec3::new(2.0, 5.0, 2.0)),
487/// NoAutoAngularInertia,
488/// ))
489/// .with_child((Collider::sphere(1.0), AngularInertia::new(Vec3::new(1.0, 2.0, 1.0))));
490/// # }
491/// ```
492///
493/// # Angular Inertia Updates
494///
495/// The [`AngularInertia`] component is never modified by the engine, so you can safely update it at any time.
496///
497/// The total [`ComputedAngularInertia`] is updated automatically whenever the mass properties of a [rigid body]
498/// or its descendants change, or when colliders are added or removed. This update is triggered by adding
499/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
500/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
501///
502/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
503/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
504///
505/// # Related Types
506///
507/// - [`ComputedAngularInertia`] stores the total angular inertia of a dynamic [rigid body] that considers child entities and colliders.
508/// - [`NoAutoAngularInertia`] disables the mass properties of child entities being taken into account for the [`ComputedAngularInertia`].
509/// - [`AngularInertiaTensor`] is the symmetric 3x3 matrix representation of angular inertia.
510/// - [`Mass`] is the linear equivalent of angular inertia, representing resistance to linear acceleration.
511/// - [`CenterOfMass`] is the local point where the mass is concentrated. Applying forces at this point produces no torque.
512/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
513/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
514///
515/// [`SystemParam`]: bevy::ecs::system::SystemParam
516#[cfg(feature = "3d")]
517#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
518#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
519#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
520#[reflect(Debug, Component, PartialEq)]
521#[doc(alias = "MomentOfInertia")]
522pub struct AngularInertia {
523 /// The principal angular inertia, representing resistance to angular acceleration
524 /// about the local coordinate axes defined by the `local_frame`.
525 pub principal: Vec3,
526 /// The orientation of the local inertial frame.
527 pub local_frame: Quat,
528}
529
530#[cfg(feature = "3d")]
531impl AngularInertia {
532 /// An angular inertia of `0.0` for all axes, with an identity local frame.
533 pub const ZERO: Self = Self {
534 principal: Vec3::ZERO,
535 local_frame: Quat::IDENTITY,
536 };
537
538 /// Creates a new [`AngularInertia`] from the given principal angular inertia.
539 ///
540 /// The principal angular inertia represents resistance to angular acceleration
541 /// about the local coordinate axes.
542 ///
543 /// To specify the orientation of the local inertial frame, consider using [`AngularInertia::new_with_local_frame`].
544 ///
545 /// # Panics
546 ///
547 /// Panics if any component of the principal angular inertia is negative or NaN when `debug_assertions` are enabled.
548 #[inline]
549 #[doc(alias = "from_principal_angular_inertia")]
550 pub fn new(principal_angular_inertia: Vec3) -> Self {
551 debug_assert!(
552 principal_angular_inertia.cmpge(Vec3::ZERO).all()
553 && !principal_angular_inertia.is_nan(),
554 "principal angular inertia must be positive or zero for all axes"
555 );
556
557 Self {
558 principal: principal_angular_inertia,
559 local_frame: Quat::IDENTITY,
560 }
561 }
562
563 /// Tries to create a new [`AngularInertia`] from the given principal angular inertia.
564 ///
565 /// The principal angular inertia represents resistance to angular acceleration
566 /// about the local coordinate axes. To specify the orientation of the local inertial frame,
567 /// consider using [`AngularInertia::try_new_with_local_frame`].
568 ///
569 /// # Errors
570 ///
571 /// Returns [`Err(AngularInertiaError)`](AngularInertiaError) if any component of the principal angular inertia is negative or NaN.
572 #[inline]
573 pub fn try_new(principal_angular_inertia: Vec3) -> Result<Self, AngularInertiaError> {
574 if principal_angular_inertia.is_nan() {
575 Err(AngularInertiaError::NaN)
576 } else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
577 Err(AngularInertiaError::Negative)
578 } else {
579 Ok(Self {
580 principal: principal_angular_inertia,
581 local_frame: Quat::IDENTITY,
582 })
583 }
584 }
585
586 /// Creates a new [`AngularInertia`] from the given principal angular inertia
587 /// and the orientation of the local inertial frame.
588 ///
589 /// The principal angular inertia represents resistance to angular acceleration
590 /// about the local coordinate axes defined by the given `local_frame`.
591 ///
592 /// # Panics
593 ///
594 /// Panics if any component of the principal angular inertia is negative or NaN when `debug_assertions` are enabled.
595 #[inline]
596 #[doc(alias = "from_principal_angular_inertia_with_local_frame")]
597 pub fn new_with_local_frame(principal_angular_inertia: Vec3, local_frame: Quat) -> Self {
598 debug_assert!(
599 principal_angular_inertia.cmpge(Vec3::ZERO).all()
600 && !principal_angular_inertia.is_nan(),
601 "principal angular inertia must be positive or zero for all axes"
602 );
603
604 Self {
605 principal: principal_angular_inertia,
606 local_frame,
607 }
608 }
609
610 /// Tries to create a new [`AngularInertia`] from the given principal angular inertia
611 /// and the orientation of the local inertial frame.
612 ///
613 /// The principal angular inertia represents resistance to angular acceleration
614 /// about the local coordinate axes defined by the given `local_frame`.
615 ///
616 /// # Errors
617 ///
618 /// Returns [`Err(AngularInertiaError)`](AngularInertiaError) if any component of the principal angular inertia is negative or NaN.
619 #[inline]
620 pub fn try_new_with_local_frame(
621 principal_angular_inertia: Vec3,
622 local_frame: Quat,
623 ) -> Result<Self, AngularInertiaError> {
624 if principal_angular_inertia.is_nan() {
625 Err(AngularInertiaError::NaN)
626 } else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
627 Err(AngularInertiaError::Negative)
628 } else {
629 Ok(Self {
630 principal: principal_angular_inertia,
631 local_frame,
632 })
633 }
634 }
635
636 /// Creates a new [`AngularInertia`] from the given [angular inertia tensor].
637 ///
638 /// The tensor should be symmetric and positive definite.
639 ///
640 /// [angular inertia tensor]: AngularInertiaTensor
641 #[inline]
642 #[doc(alias = "from_mat3")]
643 pub fn from_tensor(tensor: impl Into<AngularInertiaTensor>) -> Self {
644 let tensor = tensor.into();
645 let (principal, local_frame) = tensor.principal_angular_inertia_with_local_frame();
646
647 Self {
648 principal,
649 local_frame,
650 }
651 }
652
653 /// Creates a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
654 /// represented as a [`SymmetricMat3`].
655 ///
656 /// The tensor should be [positive-semidefinite], but this is *not* checked.
657 ///
658 /// [tensor]: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
659 /// [positive-semidefinite]: https://en.wikipedia.org/wiki/Definite_matrix
660 #[inline]
661 #[must_use]
662 #[doc(alias = "from_tensor")]
663 pub fn from_symmetric_mat3(mat: SymmetricMat3) -> Self {
664 Self::from_tensor(AngularInertiaTensor::from_symmetric_mat3(mat))
665 }
666
667 /// Tries to create a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
668 /// represented as a [`Mat3`].
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 ///
675 /// # Errors
676 ///
677 /// Returns a [`MatConversionError`] if the given matrix is not symmetric.
678 #[inline]
679 pub fn try_from_mat3(mat: Mat3) -> Result<Self, MatConversionError> {
680 SymmetricMat3::try_from_mat3(mat).map(Self::from_tensor)
681 }
682
683 /// Creates a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
684 /// represented as a [`Mat3`].
685 ///
686 /// Only the lower left triangle of the matrix is used. No check is performed to ensure
687 /// that the given matrix is truly symmetric or [positive-semidefinite].
688 ///
689 /// [tensor]: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
690 /// [positive-semidefinite]: https://en.wikipedia.org/wiki/Definite_matrix
691 #[inline]
692 #[must_use]
693 pub fn from_mat3_unchecked(mat: Mat3) -> Self {
694 Self::from_tensor(SymmetricMat3::from_mat3_unchecked(mat))
695 }
696
697 /// Computes the [`AngularInertia`] of the given shape using the given mass.
698 ///
699 /// ```
700 /// # use avian3d::prelude::*;
701 /// # use bevy::prelude::*;
702 /// #
703 /// // Compute the angular inertia from collider with a mass of `2.0`.
704 /// let inertia = AngularInertia::from_shape(&Collider::sphere(1.0), 2.0);
705 ///
706 /// // Bevy's primitive shapes can also be used.
707 /// let inertia = AngularInertia::from_shape(&Sphere::new(1.0), 2.0);
708 /// ```
709 #[inline]
710 pub fn from_shape<T: ComputeMassProperties>(shape: &T, mass: f32) -> Self {
711 let principal = shape.principal_angular_inertia(mass);
712 let local_frame = shape.local_inertial_frame();
713 Self::new_with_local_frame(principal, local_frame)
714 }
715
716 /// Returns the [`AngularInertiaTensor`] represented by this principal [`AngularInertia`]
717 /// and local inertial frame.
718 #[inline]
719 pub fn tensor(self) -> AngularInertiaTensor {
720 AngularInertiaTensor::new_with_local_frame(self.principal, self.local_frame)
721 }
722
723 /// Returns `true` if the principal angular inertia and inertial local frame are neither infinite nor NaN.
724 #[inline]
725 pub fn is_finite(self) -> bool {
726 self.principal.is_finite() && self.local_frame.is_finite()
727 }
728
729 /// Returns `true` if the principal angular inertia or inertial local frame is NaN.
730 #[inline]
731 pub fn is_nan(self) -> bool {
732 self.principal.is_nan() || self.local_frame.is_nan()
733 }
734}
735
736#[cfg(feature = "3d")]
737impl TryFrom<Mat3> for AngularInertia {
738 type Error = MatConversionError;
739
740 fn try_from(mat: Mat3) -> Result<Self, Self::Error> {
741 Self::try_from_mat3(mat)
742 }
743}
744
745#[cfg(feature = "3d")]
746impl From<SymmetricMat3> for AngularInertia {
747 fn from(tensor: SymmetricMat3) -> Self {
748 Self::from_tensor(tensor)
749 }
750}
751
752#[cfg(feature = "3d")]
753impl From<AngularInertiaTensor> for AngularInertia {
754 fn from(tensor: AngularInertiaTensor) -> Self {
755 Self::from_tensor(tensor)
756 }
757}
758
759#[cfg(feature = "3d")]
760impl From<AngularInertia> for AngularInertiaTensor {
761 fn from(inertia: AngularInertia) -> Self {
762 inertia.tensor()
763 }
764}
765
766/// The local [center of mass] of an entity, representing the average position of mass
767/// in the object. Applying forces at this point produces no torque.
768///
769/// If [`CenterOfMass`] is not present, but the entity has a [`Collider`], its [`ColliderMassProperties`]
770/// computed based on the shape will be used instead.
771///
772/// The [`CenterOfMass`] component does *not* take into account child entities, and it is never modified
773/// by the engine. The total center of mass of a dynamic [rigid body] that *does* consider child entities and colliders
774/// is stored in the [`ComputedCenterOfMass`] component. It is updated automatically when mass properties are changed,
775/// or when colliders are added or removed.
776///
777/// [center of mass]: https://en.wikipedia.org/wiki/Center_of_mass
778/// [rigid body]: RigidBody
779///
780/// # Usage
781///
782/// The [`CenterOfMass`] component can be used to define the center of mass of a [rigid body] entity or its descendants:
783///
784/// ```
785#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
786#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
787/// # use bevy::prelude::*;
788/// #
789/// # fn setup(mut commands: Commands) {
790/// // An offset of `-0.5` along the Y axis.
791/// commands.spawn((
792/// RigidBody::Dynamic,
793/// Collider::capsule(0.5, 1.5),
794#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
795#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
796/// ));
797/// # }
798/// ```
799///
800/// If no [`CenterOfMass`] is present, the [`ComputedCenterOfMass`] will be computed from the collider
801/// based on its shape.
802///
803/// ```
804#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
805#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
806/// # use bevy::prelude::*;
807/// #
808/// # fn setup(mut commands: Commands) {
809/// // For a capsule, the center of mass is at the local origin.
810/// commands.spawn((RigidBody::Dynamic, Collider::capsule(0.5, 1.5)));
811/// # }
812/// ```
813///
814/// If the rigid body has child colliders, they will contribute to the total [`ComputedCenterOfMass`]
815/// based on weighted average of their global centers of mass.
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#[cfg_attr(
824 feature = "2d",
825 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]"
826)]
827#[cfg_attr(
828 feature = "3d",
829 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]"
830)]
831/// commands.spawn((
832/// RigidBody::Dynamic,
833/// Collider::capsule(0.5, 1.5),
834/// Mass(10.0),
835#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
836#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
837/// Transform::default(),
838/// ))
839/// .with_child((
840#[cfg_attr(feature = "2d", doc = " Collider::circle(1.0),")]
841#[cfg_attr(feature = "3d", doc = " Collider::sphere(1.0),")]
842/// Mass(5.0),
843/// Transform::from_xyz(0.0, 4.0, 0.0),
844/// ));
845/// # }
846/// ```
847///
848/// To prevent the centers of mass of child entities from contributing to the total [`ComputedCenterOfMass`],
849/// add the [`NoAutoCenterOfMass`], component. This can be useful when full control over the center of mass is desired.
850///
851/// ```
852#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
853#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
854/// # use bevy::prelude::*;
855/// #
856/// # fn setup(mut commands: Commands) {
857#[cfg_attr(feature = "2d", doc = "// Total center of mass: [0.0, -0.5]")]
858#[cfg_attr(feature = "3d", doc = "// Total center of mass: [0.0, -0.5, 0.0]")]
859/// commands.spawn((
860/// RigidBody::Dynamic,
861/// Collider::capsule(0.5, 1.5),
862#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
863#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
864/// Transform::default(),
865/// ))
866/// .with_child((
867#[cfg_attr(feature = "2d", doc = " Collider::circle(1.0),")]
868#[cfg_attr(feature = "3d", doc = " Collider::sphere(1.0),")]
869/// Mass(5.0),
870/// Transform::from_xyz(0.0, 4.0, 0.0),
871/// ));
872/// # }
873/// ```
874///
875/// # Center of Mass Updates
876///
877/// The [`CenterOfMass`] component is never modified by the engine, so you can safely update it at any time.
878///
879/// The total [`ComputedCenterOfMass`] is updated automatically whenever the mass properties of a [rigid body]
880/// or its descendants change, or when colliders are added, removed, or transformed. This update is triggered by adding
881/// the [`RecomputeMassProperties`] component, which is removed after the update is performed
882/// in [`MassPropertySystems::UpdateComputedMassProperties`](super::MassPropertySystems::UpdateComputedMassProperties).
883///
884/// To immediately perform a manual update of the total mass properties for a specific rigid body entity,
885/// you can call [`MassPropertyHelper::update_mass_properties`] in a system.
886///
887/// # Related Types
888///
889/// - [`ComputedCenterOfMass`] stores the total center of mass of a dynamic [rigid body] that considers child entities and colliders.
890/// - [`NoAutoCenterOfMass`] disables the centers of mass of child entities being taken into account for the [`ComputedCenterOfMass`].
891/// - [`Mass`] represents resistance to linear acceleration.
892/// - [`AngularInertia`] is the rotational equivalent of mass, representing resistance to angular acceleration.
893/// - [`MassPropertiesBundle`] is a bundle containing mass properties.
894/// - [`MassPropertyHelper`] is a [`SystemParam`] with utilities for computing and updating mass properties.
895///
896/// [`SystemParam`]: bevy::ecs::system::SystemParam
897#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
898#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
899#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
900#[reflect(Debug, Component, Default, PartialEq)]
901pub struct CenterOfMass(pub Vector);
902
903impl CenterOfMass {
904 /// A center of mass set at the local origin.
905 pub const ZERO: Self = Self(Vector::ZERO);
906
907 /// Creates a new [`CenterOfMass`] at the given local position.
908 #[inline]
909 #[cfg(feature = "2d")]
910 pub const fn new(x: f32, y: f32) -> Self {
911 Self(Vec2::new(x, y))
912 }
913
914 /// Creates a new [`CenterOfMass`] at the given local position.
915 #[inline]
916 #[cfg(feature = "3d")]
917 pub const fn new(x: f32, y: f32, z: f32) -> Self {
918 Self(Vec3::new(x, y, z))
919 }
920
921 /// Computes the [`CenterOfMass`] of the given shape.
922 ///
923 /// ```
924 #[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
925 #[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
926 /// # use bevy::prelude::*;
927 /// #
928 /// // Compute the center of mass from a collider.
929 #[cfg_attr(
930 feature = "2d",
931 doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::circle(1.0));"
932 )]
933 #[cfg_attr(
934 feature = "3d",
935 doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::sphere(1.0));"
936 )]
937 ///
938 /// // Bevy's primitive shapes can also be used.
939 #[cfg_attr(
940 feature = "2d",
941 doc = "let center_of_mass = CenterOfMass::from_shape(&Circle::new(1.0));"
942 )]
943 #[cfg_attr(
944 feature = "3d",
945 doc = "let center_of_mass = CenterOfMass::from_shape(&Sphere::new(1.0));"
946 )]
947 /// ```
948 #[inline]
949 pub fn from_shape<T: ComputeMassProperties>(shape: &T) -> Self {
950 Self(shape.center_of_mass())
951 }
952}
953
954/// A marker component that prevents descendants or attached colliders
955/// from contributing to the total [`ComputedMass`] of a [rigid body].
956///
957/// Only the [`Mass`] component of the rigid body entity itself will be considered.
958/// This is useful when full control over mass is desired.
959///
960/// [rigid body]: RigidBody
961#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
962#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
963#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
964#[reflect(Debug, Component, Default, PartialEq)]
965#[require(RecomputeMassProperties)]
966#[component(on_remove = on_remove_no_auto_mass_property)]
967pub struct NoAutoMass;
968
969/// A marker component that prevents descendants or attached colliders
970/// from contributing to the total [`ComputedAngularInertia`] of a [rigid body].
971///
972/// Only the [`AngularInertia`] component of the rigid body entity itself will be considered.
973/// This is useful when full control over inertia is desired.
974///
975/// [rigid body]: RigidBody
976#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
977#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
978#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
979#[reflect(Debug, Component, Default, PartialEq)]
980#[require(RecomputeMassProperties)]
981#[component(on_remove = on_remove_no_auto_mass_property)]
982pub struct NoAutoAngularInertia;
983
984/// A marker component that prevents descendants or attached colliders
985/// from contributing to the total [`ComputedCenterOfMass`] of a [rigid body].
986///
987/// Only the [`CenterOfMass`] component of the rigid body entity itself will be considered.
988/// This is useful when full control over the center of mass is desired.
989///
990/// [rigid body]: RigidBody
991#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
992#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
993#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
994#[reflect(Debug, Component, Default, PartialEq)]
995#[require(RecomputeMassProperties)]
996#[component(on_remove = on_remove_no_auto_mass_property)]
997pub struct NoAutoCenterOfMass;
998
999/// Triggers the recomputation of mass properties for rigid bodies when automatic computation is re-enabled.
1000fn on_remove_no_auto_mass_property(mut world: DeferredWorld, ctx: HookContext) {
1001 if let Ok(mut entity_commands) = world.commands().get_entity(ctx.entity) {
1002 entity_commands.try_insert(RecomputeMassProperties);
1003 }
1004}
1005
1006/// A marker component that forces the recomputation of [`ComputedMass`], [`ComputedAngularInertia`]
1007/// and [`ComputedCenterOfMass`] for a [rigid body].
1008///
1009/// This is added automatically when the mass properties of a [rigid body] change, or when colliders are added or removed,
1010/// and is removed after the recomputation in [`MassPropertySystems::UpdateComputedMassProperties`].
1011///
1012/// [rigid body]: RigidBody
1013/// [`MassPropertySystems::UpdateComputedMassProperties`]: super::MassPropertySystems::UpdateComputedMassProperties
1014#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
1015#[component(storage = "SparseSet")]
1016pub struct RecomputeMassProperties;
1017
1018/// A bundle containing [mass properties].
1019///
1020/// [mass properties]: super
1021#[allow(missing_docs)]
1022#[derive(Bundle, Debug, Default, Clone, PartialEq)]
1023#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1024pub struct MassPropertiesBundle {
1025 pub mass: Mass,
1026 pub angular_inertia: AngularInertia,
1027 pub center_of_mass: CenterOfMass,
1028}
1029
1030impl MassPropertiesBundle {
1031 /// Computes the mass properties for a [`Collider`] based on its shape and a given density.
1032 ///
1033 /// ```
1034 #[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
1035 #[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
1036 /// # use bevy::prelude::*;
1037 /// #
1038 /// # fn setup(mut commands: Commands) {
1039 /// // Compute mass properties from a collider with a density of `2.0`.
1040 /// commands.spawn((
1041 /// RigidBody::Dynamic,
1042 #[cfg_attr(
1043 feature = "2d",
1044 doc = " MassPropertiesBundle::from_shape(&Collider::circle(0.5), 2.0),"
1045 )]
1046 #[cfg_attr(
1047 feature = "3d",
1048 doc = " MassPropertiesBundle::from_shape(&Collider::sphere(0.5), 2.0),"
1049 )]
1050 /// ));
1051 ///
1052 /// // Bevy's primitive shapes can also be used.
1053 /// commands.spawn((
1054 /// RigidBody::Dynamic,
1055 #[cfg_attr(
1056 feature = "2d",
1057 doc = " MassPropertiesBundle::from_shape(&Circle::new(0.5), 2.0),"
1058 )]
1059 #[cfg_attr(
1060 feature = "3d",
1061 doc = " MassPropertiesBundle::from_shape(&Sphere::new(0.5), 2.0),"
1062 )]
1063 /// ));
1064 /// # }
1065 /// ```
1066 #[inline]
1067 pub fn from_shape<T: ComputeMassProperties>(shape: &T, density: f32) -> Self {
1068 shape.mass_properties(density).to_bundle()
1069 }
1070}
1071
1072#[cfg(test)]
1073mod tests {
1074 #[cfg(feature = "3d")]
1075 use super::*;
1076 #[cfg(feature = "3d")]
1077 use approx::assert_relative_eq;
1078
1079 #[test]
1080 #[cfg(feature = "3d")]
1081 fn angular_inertia_creation() {
1082 use bevy_heavy::AngularInertiaTensor;
1083
1084 let angular_inertia = AngularInertia::new(Vec3::new(10.0, 20.0, 30.0));
1085 assert_eq!(angular_inertia.principal, Vec3::new(10.0, 20.0, 30.0));
1086 assert_eq!(angular_inertia.local_frame, Quat::IDENTITY);
1087 assert_relative_eq!(
1088 angular_inertia.tensor(),
1089 AngularInertiaTensor::new(Vec3::new(10.0, 20.0, 30.0))
1090 );
1091 }
1092
1093 #[test]
1094 #[should_panic]
1095 #[cfg(feature = "3d")]
1096 fn negative_angular_inertia_panics() {
1097 AngularInertia::new(Vec3::new(-1.0, 2.0, 3.0));
1098 }
1099
1100 #[test]
1101 #[cfg(feature = "3d")]
1102 fn negative_angular_inertia_error() {
1103 assert_eq!(
1104 AngularInertia::try_new(Vec3::new(-1.0, 2.0, 3.0)),
1105 Err(AngularInertiaError::Negative),
1106 "negative angular inertia should return an error"
1107 );
1108 }
1109
1110 #[test]
1111 #[cfg(feature = "3d")]
1112 fn nan_angular_inertia_error() {
1113 assert_eq!(
1114 AngularInertia::try_new(Vec3::new(f32::NAN, 2.0, 3.0)),
1115 Err(AngularInertiaError::NaN),
1116 "NaN angular inertia should return an error"
1117 );
1118 }
1119}