1use core::fmt;
32use crate::quaternion::Quaternion;
33use crate::vector4::V4;
34use crate::matrix4x4::M44;
35use crate::matrix3x3::M33;
36use crate::vector3::V3;
37use crate::utils::{nearly_zero, nearly_equal};
38use crate::transformations::{get_parts_raw, homogeneous_from_rotation};
39use core::ops::{Mul, Add, Sub, Neg, Div};
40use num::{Num, Float, Signed, Zero, One};
41use num::traits::FloatConst;
42
43#[derive(Copy, Debug, Clone, PartialEq)]
45pub struct DualQuaternion<T> {
46 q_real: Quaternion<T>,
48 q_dual: Quaternion<T>,
50 normalized: bool
52}
53
54impl<T> DualQuaternion<T> {
55 pub const fn new(q_real: Quaternion<T>, q_dual: Quaternion<T>) -> Self {
56 Self{q_real, q_dual, normalized: false}
57 }
58}
59
60impl<T: Num + Copy> DualQuaternion<T> {
61 pub fn real(&self) -> Quaternion<T> {
63 self.q_real
64 }
65
66 pub fn dual(&self) -> Quaternion<T> {
68 self.q_dual
69 }
70
71 pub fn new_from_rotation(r: &Quaternion<T>) -> Self {
73 Self{q_real: *r, q_dual: Quaternion::zero(), normalized: true}
74 }
75
76 pub fn new_from_point(v: &V3<T>) -> Self {
79 Self{q_real: Quaternion::one(), q_dual: Quaternion::new_imag(v), normalized: true}
80 }
81
82}
83
84impl<T: Num + Copy> Add for DualQuaternion<T> {
86 type Output = Self;
87 fn add(self, rhs: Self) -> Self {
88 Self::new(self.q_real + rhs.q_real, self.q_dual + rhs.q_dual)
89 }
90}
91
92impl<T: Num + Copy> Sub for DualQuaternion<T> {
94 type Output = Self;
95 fn sub(self, rhs: Self) -> Self {
96 Self::new(self.q_real - rhs.q_real, self.q_dual - rhs.q_dual)
97 }
98}
99
100impl<T: Num + Copy + Signed> Neg for DualQuaternion<T> {
102 type Output = Self;
103 #[inline]
104 fn neg(self) -> Self {
105 Self::new(-self.q_real, -self.q_dual)
106 }
107}
108
109impl<T: Num + Copy> Mul for DualQuaternion<T> {
111 type Output = Self;
112
113 #[inline(always)]
114 fn mul(self, rhs: Self) -> Self::Output {
115 let q_real = self.q_real * rhs.q_real;
116 let q_dual = self.q_real * rhs.q_dual + self.q_dual * rhs.q_real;
117 Self::new(q_real, q_dual)
118 }
119}
120
121impl<T: Num + Copy> Mul<T> for DualQuaternion<T> {
123 type Output = Self;
124 fn mul(self, rhs: T) -> Self::Output {
125 Self::new(self.q_real * rhs, self.q_dual * rhs)
126 }
127}
128
129impl<T: Float + Signed> Div for DualQuaternion<T> {
131 type Output = Self;
132
133 fn div(self, rhs: Self) -> Self::Output {
134 let rhs_real_sqr = rhs.q_real * rhs.q_real;
135 let prod_real = self.q_real * rhs.q_real / rhs_real_sqr;
136 let prod_dual = (rhs.q_real * self.q_dual - self.q_real * rhs.q_dual) / rhs_real_sqr;
137 Self::new(prod_real, prod_dual)
138 }
139}
140
141impl<T: Float + Signed> DualQuaternion<T> {
142 pub fn to_homogeneous(&self) -> M44<T> {
144 let (r, p) = self.to_rotation_translation();
145 homogeneous_from_rotation(&r, &p)
146 }
147
148 pub fn inverse(&self) -> Self {
150 if self.normalized {
151 self.conj()
152 } else {
153 let q_real_inv = self.q_real.inverse().expect("the zero Quaternion cannot be inverted");
154 Self::new(q_real_inv, -q_real_inv * self.q_dual * q_real_inv)
155 }
156 }
157
158 pub fn get_screw_parameters(&self) -> (V3<T>, V3<T>, T, T) {
169 let one = T::one();
170 let two = one + one;
171 let half = one / two;
172 let theta = self.real().get_angle();
173 let t = self.get_translation();
174
175 if !nearly_zero(theta) {
176 let l = self.real().imag() / T::sin(theta / two);
177 let d = t * l;
178 let cot = one / T::tan(theta / two);
179 let m = (t.cross(l) + (t - l * d) * cot) * half;
180 (l, m, theta, d)
181 } else {
182 let d = t.norm2();
183 let mut l = V3::zero();
184 if !nearly_zero(d) {
185 l = t / d;
186 }
187 let inf = T::infinity();
188 let m = V3::new_from(inf, inf, inf);
189 (l, m, theta, d)
190 }
191 }
192
193 pub fn new_from_screw_parameters(l: &V3<T>, m: &V3<T>, theta: T, d: T) -> Self {
209 let one = T::one();
210 let two = one + one;
211 let (sin, cos) = (theta / two).sin_cos();
212 let q_real = Quaternion::new(cos, *l * sin);
213 let q_dual = Quaternion::new(sin * (-d / two), *m * sin + *l * (d / two) * cos);
214 Self::new(q_real, q_dual)
215 }
216
217 pub fn pow(&self, exponent: T) -> Self {
228 let one = T::one();
229 let two = one + one;
230 let theta = two * T::acos(self.real().real());
231 let (s, c) = (theta / two).sin_cos();
232 if nearly_zero(theta) {
233 Self::new_from_point(&(self.get_translation() * exponent))
234 } else {
235 let s0 = self.real().imag() / s;
236 let d = self.dual().real() * -two / s;
237 let se = (self.dual().imag() - s0 * (d / two) * c) / s;
238
239 let (s_exp, c_exp) = (exponent * theta / two).sin_cos();
240 let q_real = Quaternion::new(c_exp, s0 * s_exp);
241 let q_dual = Quaternion::new(s_exp * -exponent * d / two, s0 * c_exp * exponent * d / two + se * s_exp);
242 Self::new(q_real, q_dual)
243 }
244 }
245 pub fn screw_lerp(begin: &Self, end: &Self, tau: T) -> Self {
257 let one = T::one();
258 let mut start = *begin;
259 if (start.real() * end.real()).real() < T::zero() {
262 start.q_real = begin.real() * -one;
263 }
264 start * (start.inverse() * *end).pow(tau)
265 }
266
267 pub fn log(&self) -> Option<Self> {
269 if self.normalized {
270 let one = T::one();
271 let two = one + one;
272 let half = one / two;
273 let (axis, angle) = self.real().axis_angle();
274 let q_real = Quaternion::new_imag(&(axis.expect("there is no axis") * half * angle));
275 let q_dual = Quaternion::new_imag(&(self.get_translation() * half));
276 Some(DualQuaternion::new(q_real, q_dual))
277 } else {
278 None
279 }
280 }
281}
282
283impl<T: Float> DualQuaternion<T> {
284
285 pub fn new_from_array(array: [T; 7]) -> Self {
287 let one = T::one();
288 let half = one / (one + one);
289 let v_q = V4::new_from(array[0], array[1], array[2], array[3]);
290 let q_real = Quaternion::new_from_vec(&v_q);
291 let v_d = V3::new_from(array[4], array[5], array[6]);
292 let q_dual = Quaternion::new_imag(&v_d) * half * q_real;
293 Self::new(q_real, q_dual)
294 }
295
296 pub fn new_from_translation(t: &V3<T>) -> Self {
298 let one = T::one();
299 let two = one + one;
300 Self{q_real: Quaternion::one(), q_dual: Quaternion::new_imag(&V3::new_from(t[0]/two, t[1]/two, t[2]/two)), normalized: true}
301 }
302
303 pub fn new_from_rot_trans(rot: &Quaternion<T>, translation: &V3<T>) -> Self {
305 let one = T::one();
306 let half = one / (one + one);
307 let q_real = rot.normalize().expect("the quaternion it can't be zero!!!");
308 let q_dual = (Quaternion::new_imag(translation) * half) * q_real;
309 Self{q_real, q_dual, normalized: true}
310 }
311
312 pub fn is_normalized(&self) -> bool {
313 if self.normalized {
314 true
315 } else {
316 if nearly_zero(self.real().norm2()) {
317 return true
318 }
319 let check1 = nearly_equal(self.real().norm2(), T::one(), T::epsilon());
320 let dual_norm = self.dual() / self.real().norm2();
321 let check2 = nearly_equal(dual_norm.real(), self.dual().real(), T::epsilon()) &&
322 nearly_equal(dual_norm.imag()[0], self.dual().imag()[0], T::epsilon()) &&
323 nearly_equal(dual_norm.imag()[1], self.dual().imag()[1], T::epsilon()) &&
324 nearly_equal(dual_norm.imag()[2], self.dual().imag()[2], T::epsilon());
325 check1 && check2
326 }
327 }
328
329}
330
331impl<T: Float + FloatConst + core::iter::Sum> DualQuaternion<T> {
332
333 pub fn normalize(&self) -> Option<Self> {
335 if self.normalized {
336 Some(*self)
337 } else {
338 let norm_q_real = self.q_real.norm2();
339 if !nearly_zero(norm_q_real) {
340 let mut result = Self::one();
341 result.q_real = self.q_real / norm_q_real;
342 result.q_dual = self.q_dual / norm_q_real;
343 result.normalized = true;
344 Some(result)
345 } else {
346 None
347 }
348 }
349 }
350
351 pub fn new_from_homogeneous(t: &M44<T>) -> Self {
355 let (rot, p) = get_parts_raw(t);
356 let q = Quaternion::from_rotation(&rot).normalize().expect("the quaternion it can't be zero!!!");
357 Self::new_from_rot_trans(&q, &p)
358 }
359
360 pub fn new_from_rotation_matrix(m: &M33<T>) -> Self {
362 Self{q_real: Quaternion::from_rotation(m), q_dual: Quaternion::zero(), normalized: true}
363 }
364}
365
366impl<T: Num + Copy + Signed> DualQuaternion<T> {
367 pub fn conj(&self) -> Self {
369 Self::new(self.q_real.conj(), self.q_dual.conj())
370 }
371
372 pub fn conj_as_dual(&self) -> Self {
374 Self::new(self.q_real, -self.q_dual)
375 }
376
377 pub fn conj_combined(&self) -> Self {
379 Self::new(self.q_real.conj(), -self.q_dual.conj())
380 }
381
382 pub fn norm(&self) -> Self {
384 *self * self.conj()
385 }
386
387 pub fn to_rotation_translation(&self) -> (M33<T>, V3<T>) {
389 let r = self.real().to_rotation();
390 let t = self.get_translation();
391 (r, t)
392 }
393
394 pub fn get_translation(&self) -> V3<T> {
396 let one = T::one();
397 let two = one + one;
398 ((self.dual() * two) * self.real().conj()).imag()
399 }
400
401 pub fn to_quaternion_translation(&self) -> (Quaternion<T>, V3<T>) {
403 let one = T::one();
404 let two = one + one;
405 let q = self.real();
406 let t = (self.dual() * two) * self.real().conj();
407 (q, t.imag())
408 }
409
410 pub fn transform_point(&self, point: &V3<T>) -> V3<T> {
413 let dq_point = Self::new_from_point(point);
414 (*self * dq_point * self.conj()).dual().imag()
415 }
416}
417
418impl<T: Num + Copy> DualQuaternion<T> {
419
420 pub fn new_from_vecs(q_real: &V4<T>, q_dual: &V4<T>) -> Self {
422 Self::new(Quaternion::new_from_vec(q_real), Quaternion::new_from_vec(q_dual))
423 }
424
425 pub fn zero() -> Self {
427 <DualQuaternion<T> as Zero>::zero()
428 }
429
430 pub fn one() -> DualQuaternion<T> {
432 <DualQuaternion<T> as One>::one()
433 }
434}
435
436impl<T: Num + Copy> Zero for DualQuaternion<T> {
438 fn zero() -> Self {
439 Self::new(Quaternion::zero(), Quaternion::zero())
440 }
441
442 fn is_zero(&self) -> bool {
443 *self == DualQuaternion::zero()
444 }
445}
446
447impl<T: Num + Copy> One for DualQuaternion<T> {
449 fn one() -> Self {
451 Self{q_real: Quaternion::one(), q_dual: Quaternion::zero(), normalized: true}
452 }
453}
454
455impl<T: Num + fmt::Display> fmt::Display for DualQuaternion<T> {
460 fn fmt(&self, dest: &mut fmt::Formatter) -> fmt::Result {
461 write!(dest, "real: {}\ndual: {}", self.q_real, self.q_dual)
462 }
463}
464
465#[cfg(test)]
469mod test_dual_quaternion {
470 use crate::dual_quaternion::DualQuaternion;
471 use crate::quaternion::Quaternion;
472 use crate::vector3::V3;
473 use crate::vector4::V4;
474 use crate::matrix3x3::M33;
475 use crate::matrix4x4::M44;
476 use crate::m44_new;
477 use crate::utils::{nearly_equal, nearly_zero, compare_vecs, compare_dual_quaternions};
478 use crate::transformations::{homogeneous_from_quaternion, euler_to_rotation};
479 const EPS: f32 = 1e-4;
480
481 #[test]
482 fn unity_product_dual_quaternion_test() {
483 let unit = DualQuaternion::one();
484 let expected: DualQuaternion<f32> = DualQuaternion::new_from_translation(&V3::z_axis());
485 let result = unit * expected;
486 assert!(compare_dual_quaternions(result, expected, EPS))
487 }
488
489 #[test]
490 fn dual_quaternion_creation_tests() {
491 let dq: DualQuaternion<f32> = DualQuaternion::new_from_translation(&V3::z_axis());
493 let result = dq.to_rotation_translation();
495 let result2 = dq.to_quaternion_translation();
496 let expected1 = V3::z_axis();
497
498 assert_eq!(
499 &result.1[..],
500 &expected1[..],
501 "\nExpected\n{:?}\nfound\n{:?}",
502 &result.1[..],
503 &expected1[..]
504 );
505
506 assert_eq!(
507 &result2.1[..],
508 &expected1[..],
509 "\nExpected\n{:?}\nfound\n{:?}",
510 &result2.1[..],
511 &expected1[..]
512 );
513
514 let expected2 = M33::identity();
515 assert!(compare_vecs(&result.0.as_vec(), &expected2.as_vec(), EPS));
516
517 let expected3 = Quaternion::one();
518 assert!(nearly_equal(result2.0.real(), expected3.real(), EPS));
519 assert!(nearly_equal(result2.0.imag()[0], expected3.imag()[0], EPS));
520 assert!(nearly_equal(result2.0.imag()[1], expected3.imag()[1], EPS));
521 assert!(nearly_equal(result2.0.imag()[2], expected3.imag()[2], EPS));
522 }
523
524 #[test]
525 fn dual_quaternion_test_norm() {
526 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));
529 let one:DualQuaternion<f32> = DualQuaternion::one();
530 let normalized = dq.normalize().unwrap();
531 assert!(normalized.is_normalized());
532 assert!(one.is_normalized());
533 assert!(compare_dual_quaternions(one, one.normalize().unwrap(), EPS))
534 }
535
536 #[test]
538 fn homogeneous_conversions() {
539 let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
540 let expected = homogeneous_from_quaternion(&q, &V3::new_from(1.0, 2.0, 3.0));
541 let result = DualQuaternion::new_from_homogeneous(&expected).to_homogeneous();
542
543 assert!(compare_vecs(&result.as_vec(), &expected.as_vec(), EPS));
544 }
545
546 #[test]
547 fn conjugate_product_test() {
548 let q1 = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
549 let q2 = Quaternion::from_euler_angles(30f32.to_radians(), 10f32.to_radians(), 30f32.to_radians());
550 let dq1 = DualQuaternion::new_from_rot_trans(&q1, &V3::x_axis());
551 let dq2 = DualQuaternion::new_from_rot_trans(&q2, &V3::z_axis());
552
553 let result = (dq1 * dq2).conj();
554 let expected = dq2.conj() * dq1.conj();
555
556 assert!(compare_dual_quaternions(result, expected, EPS));
557 }
558
559 #[test]
561 fn combined_transformations_tests() {
562 let rot = euler_to_rotation(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians(), None);
563 let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
564
565 let t_pure = DualQuaternion::new_from_translation(&V3::x_axis());
566 let r_pure = DualQuaternion::new_from_rotation(&q);
567
568 let expected = DualQuaternion::new_from_rot_trans(&q, &V3::x_axis());
569 let result1 = t_pure * r_pure;
570
571 let r_pure2 = DualQuaternion::new_from_rotation_matrix(&rot);
572 let result2 = t_pure * r_pure2;
573
574 assert!(compare_dual_quaternions(result1, expected, EPS));
575 assert!(compare_dual_quaternions(result2, expected, EPS));
576 }
577
578 #[test]
581 fn dual_quaternion_inverse_test() {
582 let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
584 let dq = DualQuaternion::new_from_rot_trans(&q, &V3::x_axis());
585 let result = dq.inverse() * dq;
586 let expected = DualQuaternion::one();
587 assert!(compare_dual_quaternions(result, expected, EPS));
588 }
589
590 #[test]
593 fn dual_quaternion_transformation_inverse() {
594 let t_1_2 = m44_new!(0.0, 1.0, 0.0, 2.0;
595 -1.0, 0.0, 0.0, 4.0;
596 0.0, 0.0, 1.0, 6.0;
597 0.0, 0.0, 0.0, 1.0);
598
599 let t_2_1 = m44_new!(0.0, -1.0, 0.0, 4.0;
600 1.0, 0.0, 0.0, -2.0;
601 0.0, 0.0, 1.0, -6.0;
602 0.0, 0.0, 0.0, 1.0);
603
604 let dq_1_2 = DualQuaternion::new_from_homogeneous(&t_1_2);
605 let dq_2_1 = DualQuaternion::new_from_homogeneous(&t_2_1);
606
607 assert!(compare_vecs(&dq_2_1.to_homogeneous().as_vec(), &dq_1_2.inverse().to_homogeneous().as_vec(), EPS));
608 assert!(compare_vecs(&dq_1_2.to_homogeneous().as_vec(), &dq_2_1.inverse().to_homogeneous().as_vec(), EPS));
609 }
610
611 #[test]
612 fn dual_quaternion_transform_point_test() {
613 let q = Quaternion::from_euler_angles(10f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
614 let t = homogeneous_from_quaternion(&q, &V3::new_from(1.0, 2.0, 3.0));
615
616 let p = V4::new_from(1.0, 2.0, 3.0, 0.0);
617 let expected = t * p;
618
619 let p = V3::new_from(1.0, 2.0, 3.0);
620 let dq = DualQuaternion::new_from_homogeneous(&t);
621 let result = dq.transform_point(&p);
622
623 assert!(nearly_equal(result[0], expected[0], EPS));
626 assert!(nearly_equal(result[1], expected[1], EPS));
627 assert!(nearly_equal(result[2], expected[2], EPS));
628 assert!(nearly_zero(expected[3]));
629 }
630
631 #[test]
632 fn dual_quaternion_get_screw_params_translation_test() {
633 use num::Float;
634 let trans = &V3::new_from(10.0, 3.7, 7.3);
635 let t_pure = DualQuaternion::new_from_translation(&trans);
636 let (l_result, m_result, theta_result, d_result) = t_pure.get_screw_parameters();
637 let inf = f32::infinity();
638 assert!(nearly_equal(l_result.norm2(), 1.0, EPS));
640 assert!(compare_vecs(&*m_result, &*V3::new_from(inf, inf, inf), EPS));
642 assert!(nearly_zero(theta_result));
644 assert!(d_result.is_finite());
646 assert!(nearly_equal(d_result, trans.norm2(), EPS))
649 }
650
651 #[test]
652 fn dual_quaternion_screw_params_rotation_test() {
653 let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
654 let r_pure = DualQuaternion::new_from_rotation(&q);
655 let (l_result, m_result, theta_result, d_result) = r_pure.get_screw_parameters();
656 assert!(nearly_equal(l_result.norm2(), 1.0, EPS));
658 assert!(compare_vecs(&*m_result, &*V3::zeros(), EPS));
660 assert!(nearly_equal(theta_result, q.get_angle(), EPS));
663 assert!(nearly_zero(d_result));
666 }
667
668 #[test]
673 fn dual_quternion_screw_paras_full() {
674 let v = V3::z_axis() * 45f32.to_radians();
675 let q = Quaternion::rotation_norm_encoded(&v);
676 let trans = V3::new_from(0.0, 0.0, 7.0);
677 let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
678 let (l, m, theta, d) = dq_full.get_screw_parameters();
679 assert!(compare_vecs(&*l, &*V3::z_axis(), EPS));
680 assert!(compare_vecs(&*m, &*V3::zeros(), EPS));
681 assert!(nearly_equal(theta, 45f32.to_radians(), EPS));
682 assert!(nearly_equal(d, 7.0, EPS));
683 }
684
685 #[test]
686 fn dual_quaternion_screw_params_test() {
687 let v = V3::z_axis() * 45f32.to_radians();
688 let q = Quaternion::rotation_norm_encoded(&v);
689 let trans = V3::new_from(0.0, 0.0, 7.0);
690 let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
691 let (l, m, theta, d) = dq_full.get_screw_parameters();
692 let result = DualQuaternion::new_from_screw_parameters(&l, &m, theta, d);
693
694 assert!(compare_dual_quaternions(dq_full, result, EPS));
695 }
696
697 #[test]
698 fn dual_quaternion_pow_test() {
699 let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
700 let trans = V3::new_from(0.0, 0.0, 7.0);
701 let dq_full = DualQuaternion::new_from_rot_trans(&q, &trans);
702 let expected = dq_full * dq_full * dq_full;
703 let result = dq_full.pow(3.0);
704
705 assert!(compare_dual_quaternions(expected, result, EPS));
706 }
707
708 #[test]
709 fn dual_quaternion_interpolation_test() {
710 let taus = [0.0, 0.37, 0.73, 1.0];
711 let q = Quaternion::from_euler_angles(73f32.to_radians(), 10f32.to_radians(), 10f32.to_radians());
712 let trans = V3::new_from(0.0, 0.0, 7.0);
713 let dq = DualQuaternion::new_from_rot_trans(&q, &trans).normalize().unwrap();
714 let (l, m, theta, d) = dq.get_screw_parameters();
715
716 for tau in &taus {
717 let result = DualQuaternion::screw_lerp(&DualQuaternion::one(), &dq, *tau);
718 let expected = DualQuaternion::new_from_screw_parameters(&l, &m, *tau * theta, *tau * d);
719 assert!(compare_dual_quaternions(expected, result, EPS));
720 }
721
722 }
723}