use core::fmt;
#[cfg(feature = "nalgebra-0_32")]
use nalgebra_0_32 as na;
#[cfg(feature = "nalgebra-0_33")]
use nalgebra_0_33 as na;
#[cfg(feature = "nalgebra-0_34")]
use nalgebra_0_34 as na;
use crate::scalar::Float;
use super::{Bivector, Rotor, Vector};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NalgebraConversionError {
NotAntisymmetric,
}
impl fmt::Display for NalgebraConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotAntisymmetric => write!(f, "matrix is not antisymmetric"),
}
}
}
impl std::error::Error for NalgebraConversionError {}
impl<T: Float + na::Scalar> From<na::Vector3<T>> for Vector<T> {
#[inline]
fn from(v: na::Vector3<T>) -> Self {
Vector::new(v.x, v.y, v.z)
}
}
impl<T: Float + na::Scalar> From<Vector<T>> for na::Vector3<T> {
#[inline]
fn from(v: Vector<T>) -> Self {
na::Vector3::new(v.x(), v.y(), v.z())
}
}
impl<T: Float + na::Scalar> From<Bivector<T>> for na::Vector3<T> {
#[inline]
fn from(b: Bivector<T>) -> Self {
na::Vector3::new(b.rx(), -b.ry(), b.rz())
}
}
impl<T: Float + na::Scalar> From<na::Vector3<T>> for Bivector<T> {
#[inline]
fn from(v: na::Vector3<T>) -> Self {
Bivector::new(v.z, -v.y, v.x)
}
}
impl<T: Float + na::Scalar> From<Bivector<T>> for na::Matrix3<T> {
#[inline]
fn from(b: Bivector<T>) -> Self {
na::Matrix3::new(
T::zero(),
-b.rz(),
-b.ry(), b.rz(),
T::zero(),
-b.rx(), b.ry(),
b.rx(),
T::zero(), )
}
}
impl<T: Float + na::Scalar> TryFrom<na::Matrix3<T>> for Bivector<T> {
type Error = NalgebraConversionError;
fn try_from(m: na::Matrix3<T>) -> Result<Self, Self::Error> {
let tol = T::from_f64(1e-10);
if m[(0, 0)].abs() > tol || m[(1, 1)].abs() > tol || m[(2, 2)].abs() > tol {
return Err(NalgebraConversionError::NotAntisymmetric);
}
if (m[(0, 1)] + m[(1, 0)]).abs() > tol
|| (m[(0, 2)] + m[(2, 0)]).abs() > tol
|| (m[(1, 2)] + m[(2, 1)]).abs() > tol
{
return Err(NalgebraConversionError::NotAntisymmetric);
}
Ok(Bivector::new(-m[(0, 1)], -m[(0, 2)], -m[(1, 2)]))
}
}
impl<T: Float + na::RealField> From<Rotor<T>> for na::UnitQuaternion<T> {
#[inline]
fn from(rotor: Rotor<T>) -> Self {
let r = rotor.normalize();
let q = na::Quaternion::new(r.s(), -r.rx(), r.ry(), -r.rz());
na::UnitQuaternion::new_normalize(q)
}
}
impl<T: Float + na::RealField> From<na::UnitQuaternion<T>> for Rotor<T> {
#[inline]
fn from(q: na::UnitQuaternion<T>) -> Self {
let q = q.quaternion();
Rotor::new_unchecked(q.w, -q.k, q.j, -q.i)
}
}
impl<T: Float + na::RealField> From<Rotor<T>> for na::Rotation3<T> {
#[inline]
fn from(rotor: Rotor<T>) -> Self {
let q: na::UnitQuaternion<T> = rotor.into();
q.into()
}
}
impl<T: Float + na::RealField> From<na::Rotation3<T>> for Rotor<T> {
#[inline]
fn from(rot: na::Rotation3<T>) -> Self {
let q: na::UnitQuaternion<T> = rot.into();
q.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::Transform;
use crate::test_utils::RELATIVE_EQ_EPS;
use crate::wrappers::Unit;
use approx::relative_eq;
use proptest::prelude::*;
proptest! {
#[test]
fn vector_roundtrip(v in any::<Vector<f64>>()) {
let na_v: na::Vector3<f64> = v.into();
let back: Vector<f64> = na_v.into();
prop_assert!(relative_eq!(v, back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn bivector_dual_roundtrip(b in any::<Bivector<f64>>()) {
let na_v: na::Vector3<f64> = b.into();
let back: Bivector<f64> = na_v.into();
prop_assert!(relative_eq!(b, back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn bivector_matrix_roundtrip(b in any::<Bivector<f64>>()) {
let m: na::Matrix3<f64> = b.into();
let back: Bivector<f64> = m.try_into().unwrap();
prop_assert!(relative_eq!(b, back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn bivector_matrix_is_antisymmetric(b in any::<Bivector<f64>>()) {
let m: na::Matrix3<f64> = b.into();
let sum = m + m.transpose();
for i in 0..3 {
for j in 0..3 {
prop_assert!(sum[(i, j)].abs() < RELATIVE_EQ_EPS);
}
}
}
#[test]
fn rotor_quaternion_roundtrip(r in any::<Unit<Rotor<f64>>>()) {
let q: na::UnitQuaternion<f64> = r.into_inner().into();
let back: Rotor<f64> = q.into();
let test_v = Vector::new(1.0, 2.0, 3.0);
let rotated_orig = r.as_inner().transform(&test_v);
let rotated_back = back.transform(&test_v);
prop_assert!(relative_eq!(rotated_orig, rotated_back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn rotor_rotation3_roundtrip(r in any::<Unit<Rotor<f64>>>()) {
let rot: na::Rotation3<f64> = r.into_inner().into();
let back: Rotor<f64> = rot.into();
let test_v = Vector::new(1.0, 2.0, 3.0);
let rotated_orig = r.as_inner().transform(&test_v);
let rotated_back = back.transform(&test_v);
prop_assert!(relative_eq!(rotated_orig, rotated_back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn rotor_quaternion_rotation_equivalence(
r in any::<Unit<Rotor<f64>>>(),
v in any::<Vector<f64>>(),
) {
let na_v: na::Vector3<f64> = v.into();
let rotated_ga = r.as_inner().transform(&v);
let q: na::UnitQuaternion<f64> = r.into_inner().into();
let rotated_na = q * na_v;
let rotated_back: Vector<f64> = rotated_na.into();
prop_assert!(relative_eq!(rotated_ga, rotated_back, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn bivector_dual_matches_method(b in any::<Bivector<f64>>()) {
let na_v: na::Vector3<f64> = b.into();
let dual_v = b.dual();
prop_assert!(relative_eq!(na_v.x, dual_v.x(), epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(na_v.y, dual_v.y(), epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(na_v.z, dual_v.z(), epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
#[test]
fn matrix_not_antisymmetric_error() {
let m = na::Matrix3::new(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(
Bivector::<f64>::try_from(m),
Err(NalgebraConversionError::NotAntisymmetric)
);
let m = na::Matrix3::new(0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(
Bivector::<f64>::try_from(m),
Err(NalgebraConversionError::NotAntisymmetric)
);
}
}