use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
use glam::DMat3;
use crate::frame::Frame;
#[repr(transparent)]
pub struct InertiaTensor<F: Frame> {
value: DMat3,
_f: PhantomData<F>,
}
impl<F: Frame> InertiaTensor<F> {
#[inline]
pub const fn from_dmat3_unchecked(value: DMat3) -> Self {
Self {
value,
_f: PhantomData,
}
}
#[inline]
pub fn zero() -> Self {
Self::from_dmat3_unchecked(DMat3::ZERO)
}
#[inline]
pub fn from_principal(ixx: f64, iyy: f64, izz: f64) -> Self {
Self::from_dmat3_unchecked(DMat3::from_diagonal(glam::DVec3::new(ixx, iyy, izz)))
}
#[inline]
pub fn from_components(ixx: f64, iyy: f64, izz: f64, ixy: f64, ixz: f64, iyz: f64) -> Self {
let m = DMat3::from_cols(
glam::DVec3::new(ixx, ixy, ixz),
glam::DVec3::new(ixy, iyy, iyz),
glam::DVec3::new(ixz, iyz, izz),
);
Self::from_dmat3_unchecked(m)
}
#[inline]
pub const fn as_dmat3(&self) -> DMat3 {
self.value
}
#[inline]
pub fn transpose(&self) -> Self {
Self::from_dmat3_unchecked(self.value.transpose())
}
#[inline]
pub fn transform(&self, rot: &DMat3) -> Self {
Self::from_dmat3_unchecked(rot.transpose() * self.value * *rot)
}
}
impl<F: Frame> Copy for InertiaTensor<F> {}
impl<F: Frame> Clone for InertiaTensor<F> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<F: Frame> core::fmt::Debug for InertiaTensor<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"InertiaTensor<{}>({:?})",
core::any::type_name::<F>(),
self.value
)
}
}
impl<F: Frame> PartialEq for InertiaTensor<F> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<F: Frame> Default for InertiaTensor<F> {
#[inline]
fn default() -> Self {
Self::zero()
}
}
impl<F: Frame> Add for InertiaTensor<F> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self::from_dmat3_unchecked(self.value + rhs.value)
}
}
impl<F: Frame> AddAssign for InertiaTensor<F> {
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.value += rhs.value;
}
}
impl<F: Frame> Sub for InertiaTensor<F> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self::from_dmat3_unchecked(self.value - rhs.value)
}
}
impl<F: Frame> SubAssign for InertiaTensor<F> {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
self.value -= rhs.value;
}
}
impl<F: Frame> Neg for InertiaTensor<F> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::from_dmat3_unchecked(-self.value)
}
}
impl<F: Frame> Mul<f64> for InertiaTensor<F> {
type Output = Self;
#[inline]
fn mul(self, scalar: f64) -> Self {
Self::from_dmat3_unchecked(self.value * scalar)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::RootInertial;
#[test]
fn principal_constructor_is_diagonal() {
let i = InertiaTensor::<RootInertial>::from_principal(2.0, 3.0, 5.0);
let m = i.as_dmat3();
assert_eq!(m.x_axis, glam::DVec3::new(2.0, 0.0, 0.0));
assert_eq!(m.y_axis, glam::DVec3::new(0.0, 3.0, 0.0));
assert_eq!(m.z_axis, glam::DVec3::new(0.0, 0.0, 5.0));
}
#[test]
fn from_components_is_symmetric() {
let i = InertiaTensor::<RootInertial>::from_components(1.0, 2.0, 3.0, 0.1, 0.2, 0.3);
let m = i.as_dmat3();
assert_eq!(m.col(0)[1], m.col(1)[0]);
assert_eq!(m.col(0)[2], m.col(2)[0]);
assert_eq!(m.col(1)[2], m.col(2)[1]);
}
#[test]
fn add_is_componentwise() {
let a = InertiaTensor::<RootInertial>::from_principal(1.0, 1.0, 1.0);
let b = InertiaTensor::<RootInertial>::from_principal(2.0, 3.0, 4.0);
assert_eq!(
(a + b).as_dmat3(),
DMat3::from_diagonal(glam::DVec3::new(3.0, 4.0, 5.0))
);
}
#[test]
fn scalar_mul_distributes() {
let i = InertiaTensor::<RootInertial>::from_principal(1.0, 2.0, 3.0);
let scaled = i * 2.0;
assert_eq!(
scaled.as_dmat3(),
DMat3::from_diagonal(glam::DVec3::new(2.0, 4.0, 6.0))
);
}
#[test]
fn transform_with_identity_is_noop() {
let i = InertiaTensor::<RootInertial>::from_components(1.0, 2.0, 3.0, 0.1, 0.2, 0.3);
let rotated = i.transform(&DMat3::IDENTITY);
assert_eq!(i, rotated);
}
}