use core::ops::Mul;
use crate::linear_algebra::{Matrix3D, Vector, Vector3D};
use crate::scalar::Numeric;
use crate::spatial::Quaternion;
use crate::spatial::lie::{inverse_left_jacobian_so3, left_jacobian_so3, skew3};
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub struct SO3<T: Numeric = f64> {
q: Quaternion<T>,
}
impl<T: Numeric> SO3<T> {
#[inline]
#[must_use]
pub fn identity() -> Self {
SO3 {
q: Quaternion::identity(),
}
}
#[inline]
#[must_use]
pub fn from_quaternion(q: Quaternion<T>) -> Self {
SO3 { q: q.normalized() }
}
#[inline]
#[must_use]
pub fn try_from_quaternion(q: Quaternion<T>) -> Option<Self> {
q.try_normalized().map(|q| SO3 { q })
}
#[inline]
#[must_use]
pub fn quaternion(self) -> Quaternion<T> {
self.q
}
#[inline]
#[must_use]
pub fn compose(self, rhs: Self) -> Self {
SO3 { q: self.q * rhs.q }
}
#[inline]
#[must_use]
pub fn inverse(self) -> Self {
SO3 {
q: self.q.conjugate(),
}
}
#[inline]
pub fn act(self, p: Vector3D<T>) -> Vector3D<T> {
self.q.transform_point(p)
}
#[inline]
#[must_use]
pub fn exp(phi: Vector3D<T>) -> Self {
SO3 {
q: Quaternion::from_scaled_axis(phi),
}
}
#[inline]
pub fn log(self) -> Vector3D<T> {
self.q.to_scaled_axis()
}
#[inline]
pub fn hat(phi: Vector3D<T>) -> Matrix3D<T> {
skew3(phi)
}
#[inline]
pub fn vee(m: Matrix3D<T>) -> Vector3D<T> {
let [[_, _, m02], [m10, _, _], [_, m21, _]] = m.into_array();
Vector::new([m21, m02, m10])
}
#[inline]
pub fn adjoint(self) -> Matrix3D<T> {
self.q.to_rotation_matrix()
}
#[inline]
pub fn to_matrix(self) -> Matrix3D<T> {
self.q.to_rotation_matrix()
}
#[inline]
#[must_use]
pub fn try_from_matrix(m: Matrix3D<T>) -> Option<Self> {
let gram = m.transpose() * m;
for row in 0..3 {
for column in 0..3 {
let target = if row == column { T::ONE } else { T::ZERO };
let error = (gram[(row, column)] - target).abs();
if !error.is_finite() || error > T::EPSILON_X30 {
return None;
}
}
}
let determinant = m.determinant();
if !determinant.is_finite() || determinant <= T::ZERO {
return None;
}
Quaternion::try_from_rotation_matrix(m).map(|q| SO3 { q })
}
#[must_use]
pub fn from_two_direction_pairs(
primary_observed: Vector3D<T>,
secondary_observed: Vector3D<T>,
primary_reference: Vector3D<T>,
secondary_reference: Vector3D<T>,
) -> Option<Self> {
if !primary_observed.is_finite()
|| !secondary_observed.is_finite()
|| !primary_reference.is_finite()
|| !secondary_reference.is_finite()
{
return None;
}
let observed_first = primary_observed.try_normalized()?;
let observed_third = observed_first.cross(secondary_observed).try_normalized()?;
let observed_second = observed_third.cross(observed_first);
let reference_first = primary_reference.try_normalized()?;
let reference_third = reference_first
.cross(secondary_reference)
.try_normalized()?;
let reference_second = reference_third.cross(reference_first);
let observed_axes = Matrix3D::from_fn(|row, column| match column {
0 => observed_first[row],
1 => observed_second[row],
_ => observed_third[row],
});
let reference_axes = Matrix3D::from_fn(|row, column| match column {
0 => reference_first[row],
1 => reference_second[row],
_ => reference_third[row],
});
SO3::try_from_matrix(reference_axes * observed_axes.transpose())
}
#[inline]
#[must_use]
pub fn interpolate(self, other: Self, t: T) -> Self {
SO3 {
q: self.q.slerp(other.q, t),
}
}
#[inline]
#[must_use]
pub fn normalized(self) -> Self {
SO3 {
q: self.q.normalized(),
}
}
#[inline]
pub fn left_jacobian(phi: Vector3D<T>) -> Matrix3D<T> {
left_jacobian_so3(phi)
}
#[inline]
pub fn right_jacobian(phi: Vector3D<T>) -> Matrix3D<T> {
left_jacobian_so3(-phi)
}
#[inline]
pub fn left_jacobian_inverse(phi: Vector3D<T>) -> Matrix3D<T> {
inverse_left_jacobian_so3(phi)
}
#[inline]
pub fn right_jacobian_inverse(phi: Vector3D<T>) -> Matrix3D<T> {
inverse_left_jacobian_so3(-phi)
}
}
impl<T: Numeric> Mul for SO3<T> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
self.compose(rhs)
}
}
impl<T: Numeric> Default for SO3<T> {
fn default() -> Self {
Self::identity()
}
}