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