#![allow(clippy::unnecessary_cast)]
use crate::prelude::*;
use bevy::prelude::*;
use derive_more::From;
use super::{AngularInertia, AngularInertiaError, CenterOfMass, Mass, MassError};
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
pub struct ComputedMass {
inverse: Scalar,
}
impl ComputedMass {
pub const INFINITY: Self = Self { inverse: 0.0 };
#[inline]
pub fn new(mass: Scalar) -> Self {
Self::from_inverse(mass.recip_or_zero())
}
#[inline]
pub fn try_new(mass: Scalar) -> Result<Self, MassError> {
if mass.is_nan() {
Err(MassError::NaN)
} else if mass < 0.0 {
Err(MassError::Negative)
} else {
Ok(Self::from_inverse(mass.recip_or_zero()))
}
}
#[inline]
pub fn from_inverse(inverse_mass: Scalar) -> Self {
debug_assert!(
inverse_mass >= 0.0 && !inverse_mass.is_nan(),
"mass must be positive or zero"
);
Self {
inverse: inverse_mass,
}
}
pub fn try_from_inverse(inverse_mass: Scalar) -> Result<Self, MassError> {
if inverse_mass.is_nan() {
Err(MassError::NaN)
} else if inverse_mass < 0.0 {
Err(MassError::Negative)
} else {
Ok(Self {
inverse: inverse_mass,
})
}
}
#[inline]
pub fn value(self) -> Scalar {
self.inverse.recip_or_zero()
}
#[inline]
pub fn inverse(self) -> Scalar {
self.inverse
}
#[inline]
pub fn set(&mut self, mass: impl Into<ComputedMass>) {
*self = mass.into();
}
#[inline]
pub fn is_finite(self) -> bool {
!self.is_infinite() && !self.is_nan()
}
#[inline]
pub fn is_infinite(self) -> bool {
self == Self::INFINITY
}
#[inline]
pub fn is_nan(self) -> bool {
self.inverse.is_nan()
}
}
impl From<Scalar> for ComputedMass {
fn from(mass: Scalar) -> Self {
Self::new(mass)
}
}
impl From<Mass> for ComputedMass {
fn from(mass: Mass) -> Self {
ComputedMass::new(mass.0 as Scalar)
}
}
impl From<ComputedMass> for Mass {
fn from(mass: ComputedMass) -> Self {
Self(mass.value() as f32)
}
}
#[cfg(feature = "2d")]
#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
#[doc(alias = "ComputedMomentOfInertia")]
pub struct ComputedAngularInertia {
inverse: Scalar,
}
#[cfg(feature = "2d")]
impl ComputedAngularInertia {
pub const INFINITY: Self = Self { inverse: 0.0 };
#[inline]
pub fn new(angular_inertia: Scalar) -> Self {
Self::from_inverse(angular_inertia.recip_or_zero())
}
#[inline]
pub fn try_new(angular_inertia: Scalar) -> Result<Self, AngularInertiaError> {
if angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if angular_inertia < 0.0 {
Err(AngularInertiaError::Negative)
} else {
Ok(Self::from_inverse(angular_inertia.recip_or_zero()))
}
}
#[inline]
pub fn from_inverse(inverse_angular_inertia: Scalar) -> Self {
debug_assert!(
inverse_angular_inertia >= 0.0 && !inverse_angular_inertia.is_nan(),
"angular inertia must be positive or zero"
);
Self {
inverse: inverse_angular_inertia,
}
}
#[inline]
pub fn try_from_inverse(inverse_angular_inertia: Scalar) -> Result<Self, AngularInertiaError> {
if inverse_angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if inverse_angular_inertia < 0.0 {
Err(AngularInertiaError::Negative)
} else {
Ok(Self {
inverse: inverse_angular_inertia,
})
}
}
#[inline]
pub fn value(self) -> Scalar {
self.inverse.recip_or_zero()
}
#[inline]
pub fn inverse(self) -> Scalar {
self.inverse
}
#[inline]
pub fn inverse_mut(&mut self) -> &mut Scalar {
&mut self.inverse
}
#[inline]
pub fn set(&mut self, angular_inertia: impl Into<ComputedAngularInertia>) {
*self = angular_inertia.into();
}
#[inline]
pub fn shifted(&self, mass: Scalar, offset: Vector) -> Scalar {
AngularInertia::from(*self).shifted(mass as f32, offset.f32()) as Scalar
}
#[inline]
pub fn shifted_inverse(&self, mass: Scalar, offset: Vector) -> Scalar {
self.shifted(mass, offset).recip_or_zero()
}
#[inline]
pub fn is_finite(self) -> bool {
!self.is_infinite() && !self.is_nan()
}
#[inline]
pub fn is_infinite(self) -> bool {
self == Self::INFINITY
}
#[inline]
pub fn is_nan(self) -> bool {
self.inverse.is_nan()
}
}
#[cfg(feature = "2d")]
impl From<Scalar> for ComputedAngularInertia {
fn from(angular_inertia: Scalar) -> Self {
Self::new(angular_inertia)
}
}
#[cfg(feature = "2d")]
impl From<AngularInertia> for ComputedAngularInertia {
fn from(inertia: AngularInertia) -> Self {
ComputedAngularInertia::new(inertia.0 as Scalar)
}
}
#[cfg(feature = "2d")]
impl From<ComputedAngularInertia> for AngularInertia {
fn from(inertia: ComputedAngularInertia) -> Self {
Self(inertia.value() as f32)
}
}
#[cfg(feature = "3d")]
#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, PartialEq)]
#[doc(alias = "ComputedMomentOfInertia")]
pub struct ComputedAngularInertia {
inverse: SymmetricMatrix,
}
impl Default for ComputedAngularInertia {
fn default() -> Self {
Self::INFINITY
}
}
#[cfg(feature = "3d")]
impl ComputedAngularInertia {
pub const INFINITY: Self = Self {
inverse: SymmetricMatrix::ZERO,
};
#[inline]
#[doc(alias = "from_principal_angular_inertia")]
pub fn new(principal_angular_inertia: Vector) -> Self {
debug_assert!(
principal_angular_inertia.cmpge(Vector::ZERO).all()
&& !principal_angular_inertia.is_nan(),
"principal angular inertia must be positive or zero for all axes"
);
Self::from_inverse_tensor(SymmetricMatrix::from_diagonal(
principal_angular_inertia.recip_or_zero(),
))
}
#[inline]
pub fn try_new(principal_angular_inertia: Vector) -> Result<Self, AngularInertiaError> {
if principal_angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if !principal_angular_inertia.cmpge(Vector::ZERO).all() {
Err(AngularInertiaError::Negative)
} else {
Ok(Self::from_inverse_tensor(SymmetricMatrix::from_diagonal(
principal_angular_inertia.recip_or_zero(),
)))
}
}
#[inline]
#[doc(alias = "from_principal_angular_inertia_with_local_frame")]
pub fn new_with_local_frame(
principal_angular_inertia: Vector,
orientation: Quaternion,
) -> Self {
debug_assert!(
principal_angular_inertia.cmpge(Vector::ZERO).all()
&& !principal_angular_inertia.is_nan(),
"principal angular inertia must be positive or zero for all axes"
);
Self::from_inverse_tensor(SymmetricMatrix::from_mat3_unchecked(
Matrix::from_quat(orientation)
* Matrix::from_diagonal(principal_angular_inertia.recip_or_zero())
* Matrix::from_quat(orientation.inverse()),
))
}
#[inline]
pub fn try_new_with_local_frame(
principal_angular_inertia: Vector,
orientation: Quaternion,
) -> Result<Self, AngularInertiaError> {
if principal_angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if !principal_angular_inertia.cmpge(Vector::ZERO).all() {
Err(AngularInertiaError::Negative)
} else {
Ok(Self::from_inverse_tensor(
SymmetricMatrix::from_mat3_unchecked(
Matrix::from_quat(orientation)
* Matrix::from_diagonal(principal_angular_inertia.recip_or_zero())
* Matrix::from_quat(orientation.inverse()),
),
))
}
}
#[inline]
#[doc(alias = "from_mat3")]
pub fn from_tensor(tensor: SymmetricMatrix) -> Self {
Self::from_inverse_tensor(tensor.inverse_or_zero())
}
#[inline]
#[doc(alias = "from_inverse_mat3")]
pub fn from_inverse_tensor(inverse_tensor: SymmetricMatrix) -> Self {
Self {
inverse: inverse_tensor,
}
}
#[inline]
pub fn value(self) -> SymmetricMatrix {
self.tensor()
}
#[inline]
pub fn inverse(self) -> SymmetricMatrix {
self.inverse_tensor()
}
#[inline]
pub(crate) fn inverse_mut(&mut self) -> &mut SymmetricMatrix {
self.inverse_tensor_mut()
}
#[inline]
#[doc(alias = "as_mat3")]
pub fn tensor(self) -> SymmetricMatrix {
self.inverse.inverse_or_zero()
}
#[inline]
#[doc(alias = "as_inverse_mat3")]
pub fn inverse_tensor(self) -> SymmetricMatrix {
self.inverse
}
#[inline]
#[doc(alias = "as_inverse_mat3_mut")]
pub fn inverse_tensor_mut(&mut self) -> &mut SymmetricMatrix {
&mut self.inverse
}
#[inline]
pub fn set(&mut self, angular_inertia: impl Into<ComputedAngularInertia>) {
*self = angular_inertia.into();
}
#[doc(alias = "diagonalize")]
pub fn principal_angular_inertia_with_local_frame(&self) -> (Vector, Quaternion) {
let angular_inertia = AngularInertia::from_tensor(self.tensor().f32());
(
angular_inertia.principal.adjust_precision(),
angular_inertia.local_frame.adjust_precision(),
)
}
#[inline]
pub fn rotated(self, rotation: Quaternion) -> Self {
let rot_mat3 = Matrix::from_quat(rotation);
Self::from_inverse_tensor(SymmetricMatrix::from_mat3_unchecked(
(rot_mat3 * self.inverse) * rot_mat3.transpose(),
))
}
#[inline]
pub fn shifted_tensor(&self, mass: Scalar, offset: Vector) -> SymmetricMatrix3 {
if mass > 0.0 && mass.is_finite() && offset != Vector::ZERO {
let diagonal_element = offset.length_squared();
let diagonal_mat = Matrix3::from_diagonal(Vector::splat(diagonal_element));
let offset_outer_product =
Matrix3::from_cols(offset * offset.x, offset * offset.y, offset * offset.z);
self.tensor()
+ SymmetricMatrix::from_mat3_unchecked((diagonal_mat + offset_outer_product) * mass)
} else {
self.tensor()
}
}
#[inline]
pub fn shifted_inverse_tensor(&self, mass: Scalar, offset: Vector) -> SymmetricMatrix3 {
self.shifted_tensor(mass, offset).inverse_or_zero()
}
#[inline]
pub fn is_finite(self) -> bool {
!self.is_infinite() && !self.is_nan()
}
#[inline]
pub fn is_infinite(self) -> bool {
self == Self::INFINITY
}
#[inline]
pub fn is_nan(self) -> bool {
self.inverse.is_nan()
}
}
#[cfg(feature = "3d")]
impl From<SymmetricMatrix> for ComputedAngularInertia {
fn from(tensor: SymmetricMatrix) -> Self {
Self::from_tensor(tensor)
}
}
#[cfg(feature = "3d")]
impl From<AngularInertia> for ComputedAngularInertia {
fn from(inertia: AngularInertia) -> Self {
ComputedAngularInertia::new_with_local_frame(
inertia.principal.adjust_precision(),
inertia.local_frame.adjust_precision(),
)
}
}
#[cfg(feature = "3d")]
impl From<ComputedAngularInertia> for AngularInertia {
fn from(inertia: ComputedAngularInertia) -> Self {
Self::from_tensor(inertia.tensor().f32())
}
}
#[cfg(feature = "2d")]
impl core::ops::Mul<Scalar> for ComputedAngularInertia {
type Output = Scalar;
#[inline]
fn mul(self, rhs: Scalar) -> Scalar {
self.value() * rhs
}
}
impl core::ops::Mul<Vector> for ComputedAngularInertia {
type Output = Vector;
#[inline]
fn mul(self, rhs: Vector) -> Vector {
self.value() * rhs
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
pub struct ComputedCenterOfMass(pub Vector);
impl ComputedCenterOfMass {
pub const ZERO: Self = Self(Vector::ZERO);
#[inline]
#[cfg(feature = "2d")]
pub const fn new(x: Scalar, y: Scalar) -> Self {
Self(Vector::new(x, y))
}
#[inline]
#[cfg(feature = "3d")]
pub const fn new(x: Scalar, y: Scalar, z: Scalar) -> Self {
Self(Vector::new(x, y, z))
}
}
impl From<CenterOfMass> for ComputedCenterOfMass {
fn from(center_of_mass: CenterOfMass) -> Self {
Self(center_of_mass.adjust_precision())
}
}
impl From<ComputedCenterOfMass> for CenterOfMass {
fn from(center_of_mass: ComputedCenterOfMass) -> Self {
Self(center_of_mass.f32())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "3d")]
use approx::assert_relative_eq;
#[test]
fn mass_creation() {
let mass = ComputedMass::new(10.0);
assert_eq!(mass, ComputedMass::from_inverse(0.1));
assert_eq!(mass.value(), 10.0);
assert_eq!(mass.inverse(), 0.1);
}
#[test]
fn zero_mass() {
let mass = ComputedMass::new(0.0);
assert_eq!(mass, ComputedMass::new(Scalar::INFINITY));
assert_eq!(mass, ComputedMass::from_inverse(0.0));
assert_eq!(mass.value(), 0.0);
assert_eq!(mass.inverse(), 0.0);
assert!(mass.is_infinite());
assert!(!mass.is_finite());
assert!(!mass.is_nan());
}
#[test]
fn infinite_mass() {
let mass = ComputedMass::INFINITY;
assert_eq!(mass, ComputedMass::new(Scalar::INFINITY));
assert_eq!(mass, ComputedMass::from_inverse(0.0));
assert_eq!(mass.value(), 0.0);
assert_eq!(mass.inverse(), 0.0);
assert!(mass.is_infinite());
assert!(!mass.is_finite());
assert!(!mass.is_nan());
}
#[test]
#[should_panic]
fn negative_mass_panics() {
ComputedMass::new(-1.0);
}
#[test]
fn negative_mass_error() {
assert_eq!(
ComputedMass::try_new(-1.0),
Err(MassError::Negative),
"negative mass should return an error"
);
}
#[test]
fn nan_mass_error() {
assert_eq!(
ComputedMass::try_new(Scalar::NAN),
Err(MassError::NaN),
"NaN mass should return an error"
);
}
#[test]
#[cfg(feature = "2d")]
fn angular_inertia_creation() {
let angular_inertia = ComputedAngularInertia::new(10.0);
assert_eq!(angular_inertia, ComputedAngularInertia::from_inverse(0.1));
assert_eq!(angular_inertia.value(), 10.0);
assert_eq!(angular_inertia.inverse(), 0.1);
}
#[test]
#[cfg(feature = "2d")]
fn zero_angular_inertia() {
let angular_inertia = ComputedAngularInertia::new(0.0);
assert_eq!(
angular_inertia,
ComputedAngularInertia::new(Scalar::INFINITY)
);
assert_eq!(angular_inertia, ComputedAngularInertia::from_inverse(0.0));
assert_eq!(angular_inertia.value(), 0.0);
assert_eq!(angular_inertia.inverse(), 0.0);
assert!(angular_inertia.is_infinite());
assert!(!angular_inertia.is_finite());
assert!(!angular_inertia.is_nan());
}
#[test]
#[cfg(feature = "2d")]
fn infinite_angular_inertia() {
let angular_inertia = ComputedAngularInertia::INFINITY;
assert_eq!(
angular_inertia,
ComputedAngularInertia::new(Scalar::INFINITY)
);
assert_eq!(angular_inertia, ComputedAngularInertia::from_inverse(0.0));
assert_eq!(angular_inertia.value(), 0.0);
assert_eq!(angular_inertia.inverse(), 0.0);
assert!(angular_inertia.is_infinite());
assert!(!angular_inertia.is_finite());
assert!(!angular_inertia.is_nan());
}
#[test]
#[should_panic]
#[cfg(feature = "2d")]
fn negative_angular_inertia_panics() {
ComputedAngularInertia::new(-1.0);
}
#[test]
#[cfg(feature = "2d")]
fn negative_angular_inertia_error() {
assert_eq!(
ComputedAngularInertia::try_new(-1.0),
Err(AngularInertiaError::Negative),
"negative angular inertia should return an error"
);
}
#[test]
#[cfg(feature = "2d")]
fn nan_angular_inertia_error() {
assert_eq!(
ComputedAngularInertia::try_new(Scalar::NAN),
Err(AngularInertiaError::NaN),
"NaN angular inertia should return an error"
);
}
#[test]
#[cfg(feature = "3d")]
fn angular_inertia_creation() {
let angular_inertia = ComputedAngularInertia::new(Vector::new(10.0, 20.0, 30.0));
assert_relative_eq!(
angular_inertia.inverse_tensor(),
ComputedAngularInertia::from_inverse_tensor(SymmetricMatrix::from_diagonal(
Vector::new(0.1, 0.05, 1.0 / 30.0)
))
.inverse_tensor()
);
assert_relative_eq!(
angular_inertia.tensor(),
SymmetricMatrix::from_diagonal(Vector::new(10.0, 20.0, 30.0))
);
assert_relative_eq!(
angular_inertia.inverse_tensor(),
SymmetricMatrix::from_diagonal(Vector::new(0.1, 0.05, 1.0 / 30.0))
);
}
#[test]
#[cfg(feature = "3d")]
fn zero_angular_inertia() {
let angular_inertia = ComputedAngularInertia::new(Vector::ZERO);
assert_eq!(
angular_inertia,
ComputedAngularInertia::new(Vector::INFINITY)
);
assert_eq!(
angular_inertia,
ComputedAngularInertia::from_inverse_tensor(SymmetricMatrix::from_diagonal(
Vector::ZERO
))
);
assert_relative_eq!(angular_inertia.tensor(), SymmetricMatrix::ZERO);
assert_relative_eq!(angular_inertia.inverse_tensor(), SymmetricMatrix::ZERO);
assert!(angular_inertia.is_infinite());
assert!(!angular_inertia.is_finite());
assert!(!angular_inertia.is_nan());
}
#[test]
#[cfg(feature = "3d")]
fn infinite_angular_inertia() {
let angular_inertia = ComputedAngularInertia::INFINITY;
assert_eq!(
angular_inertia,
ComputedAngularInertia::new(Vector::INFINITY)
);
assert_eq!(
angular_inertia,
ComputedAngularInertia::from_inverse_tensor(SymmetricMatrix::ZERO)
);
assert_relative_eq!(angular_inertia.tensor(), SymmetricMatrix::ZERO);
assert_relative_eq!(angular_inertia.inverse_tensor(), SymmetricMatrix::ZERO);
assert!(angular_inertia.is_infinite());
assert!(!angular_inertia.is_finite());
assert!(!angular_inertia.is_nan());
}
#[test]
#[should_panic]
#[cfg(feature = "3d")]
fn negative_angular_inertia_panics() {
ComputedAngularInertia::new(Vector::new(-1.0, 2.0, 3.0));
}
#[test]
#[cfg(feature = "3d")]
fn negative_angular_inertia_error() {
assert_eq!(
ComputedAngularInertia::try_new(Vector::new(-1.0, 2.0, 3.0)),
Err(AngularInertiaError::Negative),
"negative angular inertia should return an error"
);
}
#[test]
#[cfg(feature = "3d")]
fn nan_angular_inertia_error() {
assert_eq!(
ComputedAngularInertia::try_new(Vector::new(Scalar::NAN, 2.0, 3.0)),
Err(AngularInertiaError::NaN),
"NaN angular inertia should return an error"
);
}
}