use std::ops::{Add, Mul, Neg, Sub};
use super::hamilton::Quaternion;
use crate::pga::PgaMotor;
#[repr(C, align(32))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct DualQuaternion {
pub real: Quaternion, pub dual: Quaternion, }
impl Default for DualQuaternion {
#[inline(always)]
fn default() -> Self {
Self::identity()
}
}
impl DualQuaternion {
#[inline(always)]
pub const fn identity() -> Self {
Self {
real: Quaternion::identity(),
dual: Quaternion::new(0.0, 0.0, 0.0, 0.0),
}
}
#[inline(always)]
pub const fn new(real: Quaternion, dual: Quaternion) -> Self {
Self { real, dual }
}
#[inline]
pub fn from_rotation_translation(rotation: &Quaternion, translation: &[f32; 3]) -> Self {
let q_r = rotation.normalize();
let t_quat = Quaternion::new(0.0, translation[0], translation[1], translation[2]);
let half_t_qr = t_quat * q_r;
Self {
real: q_r,
dual: Quaternion::new(
0.5 * half_t_qr.w,
0.5 * half_t_qr.x,
0.5 * half_t_qr.y,
0.5 * half_t_qr.z,
),
}
}
#[inline]
pub fn from_translation(tx: f32, ty: f32, tz: f32) -> Self {
Self::from_rotation_translation(&Quaternion::identity(), &[tx, ty, tz])
}
#[inline]
pub fn from_rotation(axis: &[f32; 3], angle_rad: f32) -> Self {
let q = Quaternion::from_axis_angle(axis, angle_rad);
Self::new(q, Quaternion::new(0.0, 0.0, 0.0, 0.0))
}
#[inline]
pub fn to_rotation_translation(&self) -> (Quaternion, [f32; 3]) {
let q_r = self.real.normalize();
let q_r_conj = q_r.conjugate();
let t_quat = (self.dual * q_r_conj) * 2.0;
(q_r, [t_quat.x, t_quat.y, t_quat.z])
}
#[inline]
pub fn transform_point(&self, p: &[f32; 3]) -> [f32; 3] {
let rot_p = self.real.rotate_vector3(p);
let (_, t) = self.to_rotation_translation();
[rot_p[0] + t[0], rot_p[1] + t[1], rot_p[2] + t[2]]
}
#[inline]
pub fn transform_vector(&self, v: &[f32; 3]) -> [f32; 3] {
self.real.rotate_vector3(v)
}
#[inline(always)]
pub fn quaternion_conjugate(&self) -> Self {
Self {
real: self.real.conjugate(),
dual: self.dual.conjugate(),
}
}
#[inline(always)]
pub fn dual_conjugate(&self) -> Self {
Self {
real: self.real,
dual: -self.dual,
}
}
#[inline(always)]
pub fn total_conjugate(&self) -> Self {
Self {
real: self.real.conjugate(),
dual: -self.dual.conjugate(),
}
}
#[inline]
pub fn norm(&self) -> (f32, f32) {
let r_norm = self.real.norm();
if r_norm > 1e-12 {
let dot = self.real.w * self.dual.w
+ self.real.x * self.dual.x
+ self.real.y * self.dual.y
+ self.real.z * self.dual.z;
(r_norm, dot / r_norm)
} else {
(0.0, 0.0)
}
}
#[inline]
pub fn normalize(&self) -> Self {
let mag = self.real.norm();
if mag > 1e-12 {
let inv_mag = 1.0 / mag;
let norm_real = self.real * inv_mag;
let dot = norm_real.w * self.dual.w
+ norm_real.x * self.dual.x
+ norm_real.y * self.dual.y
+ norm_real.z * self.dual.z;
let norm_dual = Quaternion::new(
(self.dual.w - norm_real.w * dot) * inv_mag,
(self.dual.x - norm_real.x * dot) * inv_mag,
(self.dual.y - norm_real.y * dot) * inv_mag,
(self.dual.z - norm_real.z * dot) * inv_mag,
);
Self {
real: norm_real,
dual: norm_dual,
}
} else {
Self::identity()
}
}
#[inline]
pub fn inverse(&self) -> Self {
self.normalize().quaternion_conjugate()
}
#[inline]
pub fn dlb(weights: &[f32], dqs: &[Self]) -> Self {
assert_eq!(weights.len(), dqs.len(), "Weights and dual quaternions length mismatch");
assert!(!dqs.is_empty(), "Cannot blend empty dual quaternions");
let mut sum_real = Quaternion::new(0.0, 0.0, 0.0, 0.0);
let mut sum_dual = Quaternion::new(0.0, 0.0, 0.0, 0.0);
let q0 = &dqs[0];
for (i, (&w, dq)) in weights.iter().zip(dqs.iter()).enumerate() {
let sign = if i > 0 && (dq.real.w * q0.real.w + dq.real.x * q0.real.x + dq.real.y * q0.real.y + dq.real.z * q0.real.z) < 0.0 {
-1.0
} else {
1.0
};
let eff_w = w * sign;
sum_real.w += eff_w * dq.real.w;
sum_real.x += eff_w * dq.real.x;
sum_real.y += eff_w * dq.real.y;
sum_real.z += eff_w * dq.real.z;
sum_dual.w += eff_w * dq.dual.w;
sum_dual.x += eff_w * dq.dual.x;
sum_dual.y += eff_w * dq.dual.y;
sum_dual.z += eff_w * dq.dual.z;
}
Self::new(sum_real, sum_dual).normalize()
}
#[inline]
pub fn sclerp(&self, other: &Self, t: f32) -> Self {
let m1 = PgaMotor::from(*self);
let m2 = PgaMotor::from(*other);
let interp = m1.sclerp(&m2, t);
DualQuaternion::from(interp)
}
}
impl Mul for DualQuaternion {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
let real_part = self.real * rhs.real;
let dual_part = (self.real * rhs.dual) + (self.dual * rhs.real);
Self {
real: real_part,
dual: dual_part,
}
}
}
impl Add for DualQuaternion {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self {
real: self.real + rhs.real,
dual: self.dual + rhs.dual,
}
}
}
impl Sub for DualQuaternion {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self {
real: self.real - rhs.real,
dual: self.dual - rhs.dual,
}
}
}
impl Neg for DualQuaternion {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
Self {
real: -self.real,
dual: -self.dual,
}
}
}
impl From<PgaMotor> for DualQuaternion {
#[inline]
fn from(m: PgaMotor) -> Self {
Self {
real: Quaternion::new(m.s, -m.b23, -m.b31, -m.b12),
dual: Quaternion::new(-m.e0123, -m.d01, -m.d02, -m.d03),
}
}
}
impl From<DualQuaternion> for PgaMotor {
#[inline]
fn from(dq: DualQuaternion) -> Self {
PgaMotor::new(
dq.real.w,
-dq.real.x,
-dq.real.y,
-dq.real.z,
-dq.dual.x,
-dq.dual.y,
-dq.dual.z,
-dq.dual.w,
)
}
}