use core::fmt;
use crate::quaternion::Quaternion;
use crate::vector4::V4;
use crate::matrix4x4::M44;
use crate::matrix3x3::M33;
use crate::vector3::V3;
use crate::utils::{nearly_zero, nearly_equal};
use crate::transformations::{get_parts_raw, homogeneous_from_rotation};
use core::ops::{Mul, Add, Sub, Neg, Div};
use num::{Num, Float, Signed, Zero, One};
use num::traits::FloatConst;
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct DualQuaternion<T> {
q_real: Quaternion<T>,
q_dual: Quaternion<T>,
normalized: bool
}
impl<T> DualQuaternion<T> {
pub const fn new(q_real: Quaternion<T>, q_dual: Quaternion<T>) -> Self {
Self{q_real, q_dual, normalized: false}
}
}
impl<T: Num + Copy> DualQuaternion<T> {
pub fn real(&self) -> Quaternion<T> {
self.q_real
}
pub fn dual(&self) -> Quaternion<T> {
self.q_dual
}
pub fn new_from_rotation(r: &Quaternion<T>) -> Self {
Self{q_real: *r, q_dual: Quaternion::zero(), normalized: true}
}
pub fn new_from_point(v: &V3<T>) -> Self {
Self{q_real: Quaternion::one(), q_dual: Quaternion::new_imag(v), normalized: true}
}
}
impl<T: Num + Copy> Add for DualQuaternion<T> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.q_real + rhs.q_real, self.q_dual + rhs.q_dual)
}
}
impl<T: Num + Copy> Sub for DualQuaternion<T> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.q_real - rhs.q_real, self.q_dual - rhs.q_dual)
}
}
impl<T: Num + Copy + Signed> Neg for DualQuaternion<T> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::new(-self.q_real, -self.q_dual)
}
}
impl<T: Num + Copy> Mul for DualQuaternion<T> {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: Self) -> Self::Output {
let q_real = self.q_real * rhs.q_real;
let q_dual = self.q_real * rhs.q_dual + self.q_dual * rhs.q_real;
Self::new(q_real, q_dual)
}
}
impl<T: Num + Copy> Mul<T> for DualQuaternion<T> {
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Self::new(self.q_real * rhs, self.q_dual * rhs)
}
}
impl<T: Float + Signed> Div for DualQuaternion<T> {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
let rhs_real_sqr = rhs.q_real * rhs.q_real;
let prod_real = self.q_real * rhs.q_real / rhs_real_sqr;
let prod_dual = (rhs.q_real * self.q_dual - self.q_real * rhs.q_dual) / rhs_real_sqr;
Self::new(prod_real, prod_dual)
}
}
impl<T: Float + Signed> DualQuaternion<T> {
pub fn to_homogeneous(&self) -> M44<T> {
let (r, p) = self.to_rotation_translation();
homogeneous_from_rotation(&r, &p)
}
pub fn inverse(&self) -> Self {
if self.normalized {
self.conj()
} else {
let q_real_inv = self.q_real.inverse().expect("the zero Quaternion cannot be inverted");
Self::new(q_real_inv, -q_real_inv * self.q_dual * q_real_inv)
}
}
pub fn get_screw_parameters(&self) -> (V3<T>, V3<T>, T, T) {
let one = T::one();
let two = one + one;
let half = one / two;
let theta = self.real().get_angle();
let t = self.get_translation();
if !nearly_zero(theta) {
let l = self.real().imag() / T::sin(theta / two);
let d = t * l;
let cot = one / T::tan(theta / two);
let m = (t.cross(l) + (t - l * d) * cot) * half;
(l, m, theta, d)
} else {
let d = t.norm2();
let mut l = V3::zero();
if !nearly_zero(d) {
l = t / d;
}
let inf = T::infinity();
let m = V3::new_from(inf, inf, inf);
(l, m, theta, d)
}
}
pub fn new_from_screw_parameters(l: &V3<T>, m: &V3<T>, theta: T, d: T) -> Self {
let one = T::one();
let two = one + one;
let (sin, cos) = (theta / two).sin_cos();
let q_real = Quaternion::new(cos, *l * sin);
let q_dual = Quaternion::new(sin * (-d / two), *m * sin + *l * (d / two) * cos);
Self::new(q_real, q_dual)
}
pub fn pow(&self, exponent: T) -> Self {
let one = T::one();
let two = one + one;
let theta = two * T::acos(self.real().real());
let (s, c) = (theta / two).sin_cos();
if nearly_zero(theta) {
Self::new_from_point(&(self.get_translation() * exponent))
} else {
let s0 = self.real().imag() / s;
let d = self.dual().real() * -two / s;
let se = (self.dual().imag() - s0 * (d / two) * c) / s;
let (s_exp, c_exp) = (exponent * theta / two).sin_cos();
let q_real = Quaternion::new(c_exp, s0 * s_exp);
let q_dual = Quaternion::new(s_exp * -exponent * d / two, s0 * c_exp * exponent * d / two + se * s_exp);
Self::new(q_real, q_dual)
}
}
pub fn screw_lerp(begin: &Self, end: &Self, tau: T) -> Self {
let one = T::one();
let mut start = *begin;
if (start.real() * end.real()).real() < T::zero() {
start.q_real = begin.real() * -one;
}
start * (start.inverse() * *end).pow(tau)
}
pub fn log(&self) -> Option<Self> {
if self.normalized {
let one = T::one();
let two = one + one;
let half = one / two;
let (axis, angle) = self.real().axis_angle();
let q_real = Quaternion::new_imag(&(axis.expect("there is no axis") * half * angle));
let q_dual = Quaternion::new_imag(&(self.get_translation() * half));
Some(DualQuaternion::new(q_real, q_dual))
} else {
None
}
}
}
impl<T: Float> DualQuaternion<T> {
pub fn new_from_array(array: [T; 7]) -> Self {
let one = T::one();
let half = one / (one + one);
let v_q = V4::new_from(array[0], array[1], array[2], array[3]);
let q_real = Quaternion::new_from_vec(&v_q);
let v_d = V3::new_from(array[4], array[5], array[6]);
let q_dual = Quaternion::new_imag(&v_d) * half * q_real;
Self::new(q_real, q_dual)
}
pub fn new_from_translation(t: &V3<T>) -> Self {
let one = T::one();
let two = one + one;
Self{q_real: Quaternion::one(), q_dual: Quaternion::new_imag(&V3::new_from(t[0]/two, t[1]/two, t[2]/two)), normalized: true}
}
pub fn new_from_rot_trans(rot: &Quaternion<T>, translation: &V3<T>) -> Self {
let one = T::one();
let half = one / (one + one);
let q_real = rot.normalize().expect("the quaternion it can't be zero!!!");
let q_dual = (Quaternion::new_imag(translation) * half) * q_real;
Self{q_real, q_dual, normalized: true}
}
pub fn is_normalized(&self) -> bool {
if self.normalized {
true
} else {
if nearly_zero(self.real().norm2()) {
return true
}
let check1 = nearly_equal(self.real().norm2(), T::one(), T::epsilon());
let dual_norm = self.dual() / self.real().norm2();
let check2 = nearly_equal(dual_norm.real(), self.dual().real(), T::epsilon()) &&
nearly_equal(dual_norm.imag()[0], self.dual().imag()[0], T::epsilon()) &&
nearly_equal(dual_norm.imag()[1], self.dual().imag()[1], T::epsilon()) &&
nearly_equal(dual_norm.imag()[2], self.dual().imag()[2], T::epsilon());
check1 && check2
}
}
}
impl<T: Float + FloatConst + core::iter::Sum> DualQuaternion<T> {
pub fn normalize(&self) -> Option<Self> {
if self.normalized {
Some(*self)
} else {
let norm_q_real = self.q_real.norm2();
if !nearly_zero(norm_q_real) {
let mut result = Self::one();
result.q_real = self.q_real / norm_q_real;
result.q_dual = self.q_dual / norm_q_real;
result.normalized = true;
Some(result)
} else {
None
}
}
}
pub fn new_from_homogeneous(t: &M44<T>) -> Self {
let (rot, p) = get_parts_raw(t);
let q = Quaternion::from_rotation(&rot).normalize().expect("the quaternion it can't be zero!!!");
Self::new_from_rot_trans(&q, &p)
}
pub fn new_from_rotation_matrix(m: &M33<T>) -> Self {
Self{q_real: Quaternion::from_rotation(m), q_dual: Quaternion::zero(), normalized: true}
}
}
impl<T: Num + Copy + Signed> DualQuaternion<T> {
pub fn conj(&self) -> Self {
Self::new(self.q_real.conj(), self.q_dual.conj())
}
pub fn conj_as_dual(&self) -> Self {
Self::new(self.q_real, -self.q_dual)
}
pub fn conj_combined(&self) -> Self {
Self::new(self.q_real.conj(), -self.q_dual.conj())
}
pub fn norm(&self) -> Self {
*self * self.conj()
}
pub fn to_rotation_translation(&self) -> (M33<T>, V3<T>) {
let r = self.real().to_rotation();
let t = self.get_translation();
(r, t)
}
pub fn get_translation(&self) -> V3<T> {
let one = T::one();
let two = one + one;
((self.dual() * two) * self.real().conj()).imag()
}
pub fn to_quaternion_translation(&self) -> (Quaternion<T>, V3<T>) {
let one = T::one();
let two = one + one;
let q = self.real();
let t = (self.dual() * two) * self.real().conj();
(q, t.imag())
}
pub fn transform_point(&self, point: &V3<T>) -> V3<T> {
let dq_point = Self::new_from_point(point);
(*self * dq_point * self.conj()).dual().imag()
}
}
impl<T: Num + Copy> DualQuaternion<T> {
pub fn new_from_vecs(q_real: &V4<T>, q_dual: &V4<T>) -> Self {
Self::new(Quaternion::new_from_vec(q_real), Quaternion::new_from_vec(q_dual))
}
pub fn zero() -> Self {
<DualQuaternion<T> as Zero>::zero()
}
pub fn one() -> DualQuaternion<T> {
<DualQuaternion<T> as One>::one()
}
}
impl<T: Num + Copy> Zero for DualQuaternion<T> {
fn zero() -> Self {
Self::new(Quaternion::zero(), Quaternion::zero())
}
fn is_zero(&self) -> bool {
*self == DualQuaternion::zero()
}
}
impl<T: Num + Copy> One for DualQuaternion<T> {
fn one() -> Self {
Self{q_real: Quaternion::one(), q_dual: Quaternion::zero(), normalized: true}
}
}
impl<T: Num + fmt::Display> fmt::Display for DualQuaternion<T> {
fn fmt(&self, dest: &mut fmt::Formatter) -> fmt::Result {
write!(dest, "real: {}\ndual: {}", self.q_real, self.q_dual)
}
}
#[cfg(test)]
mod test_dual_quaternion {
use crate::dual_quaternion::DualQuaternion;
use crate::quaternion::Quaternion;
use crate::vector3::V3;
use crate::vector4::V4;
use crate::matrix3x3::M33;
use crate::matrix4x4::M44;
use crate::m44_new;
use crate::utils::{nearly_equal, nearly_zero, compare_vecs, compare_dual_quaternions};
use crate::transformations::{homogeneous_from_quaternion, euler_to_rotation};
const EPS: f32 = 1e-4;
#[test]
fn unity_product_dual_quaternion_test() {
let unit = DualQuaternion::one();
let expected: DualQuaternion<f32> = DualQuaternion::new_from_translation(&V3::z_axis());
let result = unit * expected;
assert!(compare_dual_quaternions(result, expected, EPS))
}
#[test]
fn dual_quaternion_creation_tests() {
let dq: DualQuaternion<f32> = DualQuaternion::new_from_translation(&V3::z_axis());
let result = dq.to_rotation_translation();
let result2 = dq.to_quaternion_translation();
let expected1 = V3::z_axis();
assert_eq!(
&result.1[..],
&expected1[..],
"\nExpected\n{:?}\nfound\n{:?}",
&result.1[..],
&expected1[..]
);
assert_eq!(
&result2.1[..],
&expected1[..],
"\nExpected\n{:?}\nfound\n{:?}",
&result2.1[..],
&expected1[..]
);
let expected2 = M33::identity();
assert!(compare_vecs(&result.0.as_vec(), &expected2.as_vec(), EPS));
let expected3 = Quaternion::one();
assert!(nearly_equal(result2.0.real(), expected3.real(), EPS));
assert!(nearly_equal(result2.0.imag()[0], expected3.imag()[0], EPS));
assert!(nearly_equal(result2.0.imag()[1], expected3.imag()[1], EPS));
assert!(nearly_equal(result2.0.imag()[2], expected3.imag()[2], EPS));
}
#[test]
fn dual_quaternion_test_norm() {
let dq = DualQuaternion::new_from_vecs(&V4::new_from(1.0, 2.0, 3.0, 4.0), &V4::new_from(5.0, 6.0, 7.0, 8.0));
let one:DualQuaternion<f32> = DualQuaternion::one();
let normalized = dq.normalize().unwrap();
assert!(normalized.is_normalized());
assert!(one.is_normalized());
assert!(compare_dual_quaternions(one, one.normalize().unwrap(), EPS))
}
#[test]
fn homogeneous_conversions() {
let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let expected = homogeneous_from_quaternion(&q, &V3::new_from(1.0, 2.0, 3.0));
let result = DualQuaternion::new_from_homogeneous(&expected).to_homogeneous();
assert!(compare_vecs(&result.as_vec(), &expected.as_vec(), EPS));
}
#[test]
fn conjugate_product_test() {
let q1 = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let q2 = Quaternion::from_euler_angles(30f32.to_radians(), 10f32.to_radians(), 30f32.to_radians());
let dq1 = DualQuaternion::new_from_rot_trans(&q1, &V3::x_axis());
let dq2 = DualQuaternion::new_from_rot_trans(&q2, &V3::z_axis());
let result = (dq1 * dq2).conj();
let expected = dq2.conj() * dq1.conj();
assert!(compare_dual_quaternions(result, expected, EPS));
}
#[test]
fn combined_transformations_tests() {
let rot = euler_to_rotation(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians(), None);
let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let t_pure = DualQuaternion::new_from_translation(&V3::x_axis());
let r_pure = DualQuaternion::new_from_rotation(&q);
let expected = DualQuaternion::new_from_rot_trans(&q, &V3::x_axis());
let result1 = t_pure * r_pure;
let r_pure2 = DualQuaternion::new_from_rotation_matrix(&rot);
let result2 = t_pure * r_pure2;
assert!(compare_dual_quaternions(result1, expected, EPS));
assert!(compare_dual_quaternions(result2, expected, EPS));
}
#[test]
fn dual_quaternion_inverse_test() {
let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let dq = DualQuaternion::new_from_rot_trans(&q, &V3::x_axis());
let result = dq.inverse() * dq;
let expected = DualQuaternion::one();
assert!(compare_dual_quaternions(result, expected, EPS));
}
#[test]
fn dual_quaternion_transformation_inverse() {
let t_1_2 = m44_new!(0.0, 1.0, 0.0, 2.0;
-1.0, 0.0, 0.0, 4.0;
0.0, 0.0, 1.0, 6.0;
0.0, 0.0, 0.0, 1.0);
let t_2_1 = m44_new!(0.0, -1.0, 0.0, 4.0;
1.0, 0.0, 0.0, -2.0;
0.0, 0.0, 1.0, -6.0;
0.0, 0.0, 0.0, 1.0);
let dq_1_2 = DualQuaternion::new_from_homogeneous(&t_1_2);
let dq_2_1 = DualQuaternion::new_from_homogeneous(&t_2_1);
assert!(compare_vecs(&dq_2_1.to_homogeneous().as_vec(), &dq_1_2.inverse().to_homogeneous().as_vec(), EPS));
assert!(compare_vecs(&dq_1_2.to_homogeneous().as_vec(), &dq_2_1.inverse().to_homogeneous().as_vec(), EPS));
}
#[test]
fn dual_quaternion_transform_point_test() {
let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let t = homogeneous_from_quaternion(&q, &V3::new_from(1.0, 2.0, 3.0));
let p = V4::new_from(1.0, 2.0, 3.0, 0.0);
let expected = t * p;
let p = V3::new_from(1.0, 2.0, 3.0);
let dq = DualQuaternion::new_from_homogeneous(&t);
let result = dq.transform_point(&p);
assert!(nearly_equal(result[0], expected[0], EPS));
assert!(nearly_equal(result[1], expected[1], EPS));
assert!(nearly_equal(result[2], expected[2], EPS));
assert!(nearly_zero(expected[3]));
}
#[test]
fn dual_quaternion_get_screw_params_translation_test() {
use num::Float;
let trans = &V3::new_from(10.0, 3.7, 7.3);
let t_pure = DualQuaternion::new_from_translation(&trans);
let (l_result, m_result, theta_result, d_result) = t_pure.get_screw_parameters();
let inf = f32::infinity();
assert!(nearly_equal(l_result.norm2(), 1.0, EPS));
assert!(compare_vecs(&*m_result, &*V3::new_from(inf, inf, inf), EPS));
assert!(nearly_zero(theta_result));
assert!(d_result.is_finite());
assert!(nearly_equal(d_result, trans.norm2(), EPS))
}
#[test]
fn dual_quaternion_screw_params_rotation_test() {
let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let r_pure = DualQuaternion::new_from_rotation(&q);
let (l_result, m_result, theta_result, d_result) = r_pure.get_screw_parameters();
assert!(nearly_equal(l_result.norm2(), 1.0, EPS));
assert!(compare_vecs(&*m_result, &*V3::zeros(), EPS));
assert!(nearly_equal(theta_result, q.get_angle(), EPS));
assert!(nearly_zero(d_result));
}
#[test]
fn dual_quternion_screw_paras_full() {
let v = V3::z_axis() * 45f32.to_radians();
let q = Quaternion::rotation_norm_encoded(&v);
let trans = V3::new_from(0.0, 0.0, 7.0);
let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
let (l, m, theta, d) = dq_full.get_screw_parameters();
assert!(compare_vecs(&*l, &*V3::z_axis(), EPS));
assert!(compare_vecs(&*m, &*V3::zeros(), EPS));
assert!(nearly_equal(theta, 45f32.to_radians(), EPS));
assert!(nearly_equal(d, 7.0, EPS));
}
#[test]
fn dual_quaternion_screw_params_test() {
let v = V3::z_axis() * 45f32.to_radians();
let q = Quaternion::rotation_norm_encoded(&v);
let trans = V3::new_from(0.0, 0.0, 7.0);
let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
let (l, m, theta, d) = dq_full.get_screw_parameters();
let result = DualQuaternion::new_from_screw_parameters(&l, &m, theta, d);
assert!(compare_dual_quaternions(dq_full, result, EPS));
}
#[test]
fn dual_quaternion_pow_test() {
let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let trans = V3::new_from(0.0, 0.0, 7.0);
let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
let expected = dq_full * dq_full * dq_full;
let result = dq_full.pow(3.0);
assert!(compare_dual_quaternions(expected, result, EPS));
}
#[test]
fn dual_quaternion_interpolation_test() {
let taus = [0.0, 0.37, 0.73, 1.0];
let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
let trans = V3::new_from(0.0, 0.0, 7.0);
let dq = DualQuaternion::new_from_rot_trans(&q, &trans).normalize().unwrap();
let (l, m, theta, d) = dq.get_screw_parameters();
for tau in &taus {
let result = DualQuaternion::screw_lerp(&DualQuaternion::one(), &dq, *tau);
let expected = DualQuaternion::new_from_screw_parameters(&l, &m, *tau * theta, *tau * d);
assert!(compare_dual_quaternions(expected, result, EPS));
}
}
}