bevy_heavy/dim3/
angular_inertia.rs

1use core::ops::*;
2
3use bevy_math::{Mat3, Quat, Vec3};
4#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
5use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
6use glam_matrix_extras::{MatConversionError, SymmetricEigen3, SymmetricMat3};
7
8/// An error returned for an invalid [`AngularInertiaTensor`] in 3D.
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum AngularInertiaTensorError {
11    /// The mass is negative or NaN.
12    Negative,
13    /// The mass is NaN.
14    Nan,
15}
16
17/// The 3x3 [angular inertia] tensor of a 3D object, representing resistance to angular acceleration.
18///
19/// The [inertia tensor] is a [symmetric], [positive-semidefinite] matrix that describes the moment of inertia
20/// for rotations about the X, Y, and Z axes. By [diagonalizing] this matrix, it is possible to extract
21/// the [principal axes of inertia] (a [`Vec3`]) and a local inertial frame (a [`Quat`]) that defines
22/// the XYZ axes. This diagonalization can be performed using the [`principal_angular_inertia_with_local_frame`] method.
23///
24/// The diagonalized representation is more compact and often easier to work with,
25/// but the full tensor can be more efficient for computations using the angular inertia.
26///
27/// [angular inertia]: crate#angular-inertia
28/// [inertia tensor]: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
29/// [symmetric]: https://en.wikipedia.org/wiki/Symmetric_matrix
30/// [positive-semidefinite]: https://en.wikipedia.org/wiki/Definite_matrix
31/// [diagonalizing]: https://en.wikipedia.org/wiki/Diagonalizable_matrix#Diagonalization
32/// [principal axes of inertia]: https://en.wikipedia.org/wiki/Moment_of_inertia#Principal_axes
33/// [`principal_angular_inertia_with_local_frame`]: AngularInertiaTensor::principal_angular_inertia_with_local_frame
34#[derive(Clone, Copy, Debug, Default, PartialEq)]
35#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
36#[cfg_attr(feature = "bevy_reflect", reflect(Debug, PartialEq))]
37#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
38#[cfg_attr(
39    all(feature = "bevy_reflect", feature = "serialize"),
40    reflect(Serialize, Deserialize)
41)]
42#[doc(alias = "MomentOfInertiaTensor")]
43pub struct AngularInertiaTensor(SymmetricMat3);
44
45impl Deref for AngularInertiaTensor {
46    type Target = SymmetricMat3;
47
48    #[inline]
49    fn deref(&self) -> &Self::Target {
50        &self.0
51    }
52}
53
54impl AngularInertiaTensor {
55    /// Zero angular inertia.
56    pub const ZERO: Self = Self(SymmetricMat3::ZERO);
57
58    /// An angular inertia tensor with a principal angular inertia of `1.0` along the diagonal.
59    pub const IDENTITY: Self = Self(SymmetricMat3::IDENTITY);
60
61    /// Infinite angular inertia.
62    pub const INFINITY: Self = Self(SymmetricMat3::from_diagonal(Vec3::INFINITY));
63
64    /// Creates a new [`AngularInertiaTensor`] from the given principal angular inertia.
65    ///
66    /// The principal angular inertia represents the torque needed for a desired angular acceleration
67    /// about the local coordinate axes. To specify the orientation of the local inertial frame,
68    /// consider using [`new_with_local_frame`](AngularInertiaTensor::new_with_local_frame).
69    ///
70    /// # Panics
71    ///
72    /// Panics if any component of the principal angular inertia is negative or NaN
73    /// when `debug_assertions` are enabled.
74    #[inline]
75    #[doc(alias = "from_principal_angular_inertia")]
76    pub fn new(principal_angular_inertia: Vec3) -> Self {
77        debug_assert!(
78            principal_angular_inertia.cmpge(Vec3::ZERO).all(),
79            "principal angular inertia must be positive or zero for all axes"
80        );
81
82        Self(SymmetricMat3::from_diagonal(principal_angular_inertia))
83    }
84
85    /// Tries to create a new [`AngularInertiaTensor`] from the given principal angular inertia.
86    ///
87    /// The principal angular inertia represents the torque needed for a desired angular acceleration
88    /// about the local coordinate axes. To specify the orientation of the local inertial frame,
89    /// consider using [`try_new_with_local_frame`](AngularInertiaTensor::try_new_with_local_frame).
90    ///
91    /// # Errors
92    ///
93    /// Returns [`Err(AngularInertiaTensorError)`](AngularInertiaTensorError) if any component
94    /// of the principal angular inertia is negative or NaN.
95    #[inline]
96    pub fn try_new(principal_angular_inertia: Vec3) -> Result<Self, AngularInertiaTensorError> {
97        if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
98            Err(AngularInertiaTensorError::Negative)
99        } else if principal_angular_inertia.is_nan() {
100            Err(AngularInertiaTensorError::Nan)
101        } else {
102            Ok(Self(SymmetricMat3::from_diagonal(
103                principal_angular_inertia,
104            )))
105        }
106    }
107
108    /// Creates a new [`AngularInertiaTensor`] from the given principal angular inertia
109    /// and the orientation of the local inertial frame.
110    ///
111    /// The principal angular inertia represents the torque needed for a desired angular acceleration
112    /// about the local coordinate axes defined by the given `orientation`.
113    ///
114    /// # Panics
115    ///
116    /// Panics if any component of the principal angular inertia is negative or NaN
117    /// when `debug_assertions` are enabled.
118    #[inline]
119    #[doc(alias = "from_principal_angular_inertia_with_local_frame")]
120    pub fn new_with_local_frame(principal_angular_inertia: Vec3, orientation: Quat) -> Self {
121        debug_assert!(
122            principal_angular_inertia.cmpge(Vec3::ZERO).all(),
123            "principal angular inertia must be positive or zero for all axes"
124        );
125
126        Self(SymmetricMat3::from_mat3_unchecked(
127            Mat3::from_quat(orientation)
128                * Mat3::from_diagonal(principal_angular_inertia)
129                * Mat3::from_quat(orientation.inverse()),
130        ))
131    }
132
133    /// Tries to create a new [`AngularInertiaTensor`] from the given principal angular inertia
134    /// and the orientation of the local inertial frame.
135    ///
136    /// The principal angular inertia represents the torque needed for a desired angular acceleration
137    /// about the local coordinate axes defined by the given `orientation`.
138    ///
139    /// # Errors
140    ///
141    /// Returns [`Err(AngularInertiaTensorError)`](AngularInertiaTensorError) if any component
142    /// of the principal angular inertia is negative or NaN.
143    #[inline]
144    pub fn try_new_with_local_frame(
145        principal_angular_inertia: Vec3,
146        orientation: Quat,
147    ) -> Result<Self, AngularInertiaTensorError> {
148        if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
149            Err(AngularInertiaTensorError::Negative)
150        } else if principal_angular_inertia.is_nan() {
151            Err(AngularInertiaTensorError::Nan)
152        } else {
153            Ok(Self(SymmetricMat3::from_mat3_unchecked(
154                Mat3::from_quat(orientation)
155                    * Mat3::from_diagonal(principal_angular_inertia)
156                    * Mat3::from_quat(orientation.inverse()),
157            )))
158        }
159    }
160
161    /// Creates a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
162    /// represented as a [`SymmetricMat3`].
163    ///
164    /// The tensor should be [positive-semidefinite], but this is *not* checked.
165    ///
166    /// [tensor]: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
167    /// [positive-semidefinite]: https://en.wikipedia.org/wiki/Definite_matrix
168    #[inline]
169    #[must_use]
170    #[doc(alias = "from_tensor")]
171    pub const fn from_symmetric_mat3(mat: SymmetricMat3) -> Self {
172        Self(mat)
173    }
174
175    /// Tries to create a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
176    /// represented as a [`Mat3`].
177    ///
178    /// The tensor should be [positive-semidefinite], but this is *not* checked.
179    ///
180    /// [tensor]: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
181    /// [positive-semidefinite]: https://en.wikipedia.org/wiki/Definite_matrix
182    ///
183    /// # Errors
184    ///
185    /// Returns a [`MatConversionError`] if the given matrix is not symmetric.
186    #[inline]
187    pub fn try_from_mat3(mat: Mat3) -> Result<Self, MatConversionError> {
188        SymmetricMat3::try_from_mat3(mat).map(Self)
189    }
190
191    /// Creates a new [`AngularInertiaTensor`] from the given angular inertia [tensor]
192    /// represented as a [`Mat3`].
193    ///
194    /// Only the lower left triangle of the matrix is used. No check is performed to ensure
195    /// that the given matrix is truly symmetric or positive-semidefinite.
196    #[inline]
197    #[must_use]
198    pub const fn from_mat3_unchecked(mat: Mat3) -> Self {
199        Self(SymmetricMat3::from_mat3_unchecked(mat))
200    }
201
202    /// Returns the angular inertia tensor as a [`SymmetricMat3`].
203    ///
204    /// Equivalent to [`value`](AngularInertiaTensor::value).
205    #[inline]
206    #[doc(alias = "as_tensor")]
207    pub fn as_symmetric_mat3(&self) -> SymmetricMat3 {
208        self.0
209    }
210
211    /// Returns a mutable reference to the [`SymmetricMat3`] stored in `self`.
212    ///
213    /// Equivalent to [`value_mut`](AngularInertiaTensor::value_mut).
214    #[inline]
215    #[doc(alias = "as_tensor_mut")]
216    pub fn as_symmetric_mat3_mut(&mut self) -> &mut SymmetricMat3 {
217        &mut self.0
218    }
219
220    /// Returns the angular inertia tensor as a [`SymmetricMat3`].
221    ///
222    /// Equivalent to [`as_symmetric_mat3`](AngularInertiaTensor::as_symmetric_mat3).
223    #[inline]
224    pub fn value(self) -> SymmetricMat3 {
225        self.0
226    }
227
228    /// Returns a mutable reference to the [`SymmetricMat3`] stored in `self`.
229    ///
230    /// Equivalent to [`as_symmetric_mat3_mut`](AngularInertiaTensor::as_symmetric_mat3_mut).
231    #[inline]
232    pub fn value_mut(&mut self) -> &mut SymmetricMat3 {
233        &mut self.0
234    }
235
236    /// Returns the angular inertia tensor as a [`Mat3`].
237    #[inline]
238    #[doc(alias = "to_tensor")]
239    pub fn to_mat3(&self) -> Mat3 {
240        self.0.to_mat3()
241    }
242
243    /// Returns the inverse of the angular inertia tensor.
244    #[inline]
245    pub fn inverse(self) -> Self {
246        Self(self.inverse_or_zero())
247    }
248
249    /// Sets the angular inertia tensor to the given value.
250    #[inline]
251    pub fn set(&mut self, angular_inertia: impl Into<AngularInertiaTensor>) {
252        *self = angular_inertia.into();
253    }
254
255    /// Computes the principal angular inertia and local inertial frame
256    /// by diagonalizing the 3x3 tensor matrix.
257    ///
258    /// The principal angular inertia represents the torque needed for a desired angular acceleration
259    /// about the local coordinate axes defined by the local inertial frame.
260    #[doc(alias = "diagonalize")]
261    pub fn principal_angular_inertia_with_local_frame(&self) -> (Vec3, Quat) {
262        let mut eigen = SymmetricEigen3::new(self.0).reverse();
263
264        if eigen.eigenvectors.determinant() < 0.0 {
265            core::mem::swap(
266                &mut eigen.eigenvectors.y_axis,
267                &mut eigen.eigenvectors.z_axis,
268            );
269            core::mem::swap(&mut eigen.eigenvalues.y, &mut eigen.eigenvalues.z);
270        }
271
272        let mut local_inertial_frame = Quat::from_mat3(&eigen.eigenvectors).normalize();
273
274        if !local_inertial_frame.is_finite() {
275            local_inertial_frame = Quat::IDENTITY;
276        }
277
278        // Clamp eigenvalues to be non-negative.
279        let principal_angular_inertia = eigen.eigenvalues.max(Vec3::ZERO);
280
281        (principal_angular_inertia, local_inertial_frame)
282    }
283
284    /// Computes the angular inertia tensor with the given rotation.
285    ///
286    /// This can be used to transform local angular inertia to world space.
287    #[inline]
288    pub fn rotated(self, rotation: Quat) -> Self {
289        let rot_mat3 = Mat3::from_quat(rotation);
290        Self::from_mat3_unchecked((rot_mat3 * self.0) * rot_mat3.transpose())
291    }
292
293    /// Computes the angular inertia tensor shifted by the given offset, taking into account the given mass.
294    #[inline]
295    pub fn shifted(self, mass: f32, offset: Vec3) -> Self {
296        if offset != Vec3::ZERO {
297            // https://en.wikipedia.org/wiki/Parallel_axis_theorem#Tensor_generalization
298            let diagonal_element = offset.length_squared();
299            let diagonal_mat = Mat3::from_diagonal(Vec3::splat(diagonal_element));
300            let offset_outer_product =
301                Mat3::from_cols(offset * offset.x, offset * offset.y, offset * offset.z);
302            Self::from_mat3_unchecked(self.0 + mass * (diagonal_mat - offset_outer_product))
303        } else {
304            self
305        }
306    }
307}
308
309impl From<SymmetricMat3> for AngularInertiaTensor {
310    #[inline]
311    fn from(angular_inertia: SymmetricMat3) -> Self {
312        Self::from_symmetric_mat3(angular_inertia)
313    }
314}
315
316impl TryFrom<Mat3> for AngularInertiaTensor {
317    type Error = MatConversionError;
318
319    #[inline]
320    fn try_from(angular_inertia: Mat3) -> Result<Self, Self::Error> {
321        Self::try_from_mat3(angular_inertia)
322    }
323}
324
325impl From<AngularInertiaTensor> for SymmetricMat3 {
326    #[inline]
327    fn from(angular_inertia: AngularInertiaTensor) -> Self {
328        angular_inertia.0
329    }
330}
331
332impl From<AngularInertiaTensor> for Mat3 {
333    #[inline]
334    fn from(angular_inertia: AngularInertiaTensor) -> Self {
335        angular_inertia.0.to_mat3()
336    }
337}
338
339impl TryFrom<Vec3> for AngularInertiaTensor {
340    type Error = AngularInertiaTensorError;
341
342    #[inline]
343    fn try_from(principal_angular_inertia: Vec3) -> Result<Self, Self::Error> {
344        Self::try_new(principal_angular_inertia)
345    }
346}
347
348impl Add<AngularInertiaTensor> for AngularInertiaTensor {
349    type Output = Self;
350
351    #[inline]
352    fn add(self, rhs: AngularInertiaTensor) -> Self {
353        Self(self.0 + rhs.0)
354    }
355}
356
357impl AddAssign<AngularInertiaTensor> for AngularInertiaTensor {
358    #[inline]
359    fn add_assign(&mut self, rhs: AngularInertiaTensor) {
360        self.0 += rhs.0;
361    }
362}
363
364impl Mul<AngularInertiaTensor> for f32 {
365    type Output = AngularInertiaTensor;
366
367    #[inline]
368    fn mul(self, rhs: AngularInertiaTensor) -> AngularInertiaTensor {
369        AngularInertiaTensor(self * rhs.0)
370    }
371}
372
373impl MulAssign<f32> for AngularInertiaTensor {
374    #[inline]
375    fn mul_assign(&mut self, rhs: f32) {
376        self.0 *= rhs;
377    }
378}
379
380impl Div<f32> for AngularInertiaTensor {
381    type Output = Self;
382
383    #[inline]
384    fn div(self, rhs: f32) -> Self {
385        Self(self.0 / rhs)
386    }
387}
388
389impl DivAssign<f32> for AngularInertiaTensor {
390    #[inline]
391    fn div_assign(&mut self, rhs: f32) {
392        self.0 /= rhs;
393    }
394}
395
396impl Mul<AngularInertiaTensor> for Quat {
397    type Output = AngularInertiaTensor;
398
399    #[inline]
400    fn mul(self, angular_inertia: AngularInertiaTensor) -> AngularInertiaTensor {
401        angular_inertia.rotated(self)
402    }
403}
404
405impl Mul<Vec3> for AngularInertiaTensor {
406    type Output = Vec3;
407
408    #[inline]
409    fn mul(self, rhs: Vec3) -> Vec3 {
410        self.0 * rhs
411    }
412}
413
414#[cfg(any(feature = "approx", test))]
415impl approx::AbsDiffEq for AngularInertiaTensor {
416    type Epsilon = f32;
417    fn default_epsilon() -> f32 {
418        f32::EPSILON
419    }
420    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
421        self.0.abs_diff_eq(&other.0, epsilon)
422    }
423}
424
425#[cfg(any(feature = "approx", test))]
426impl approx::RelativeEq for AngularInertiaTensor {
427    fn default_max_relative() -> f32 {
428        f32::EPSILON
429    }
430    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
431        self.0.relative_eq(&other.0, epsilon, max_relative)
432    }
433}
434
435#[cfg(any(feature = "approx", test))]
436impl approx::UlpsEq for AngularInertiaTensor {
437    fn default_max_ulps() -> u32 {
438        4
439    }
440    fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
441        self.0.ulps_eq(&other.0, epsilon, max_ulps)
442    }
443}