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