1use crate::approxeq::ApproxEq;
11use crate::trig::Trig;
12use crate::{point2, point3, vec3, Angle, Point2D, Point3D, Vector2D, Vector3D};
13use crate::{Transform2D, Transform3D, UnknownUnit};
14
15use core::cmp::{Eq, PartialEq};
16use core::fmt;
17use core::hash::Hash;
18use core::marker::PhantomData;
19use core::ops::{Add, Mul, Neg, Sub};
20
21#[cfg(feature = "bytemuck")]
22use bytemuck::{Pod, Zeroable};
23use num_traits::real::Real;
24use num_traits::{NumCast, One, Zero};
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28#[repr(C)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[cfg_attr(
32 feature = "serde",
33 serde(bound(
34 serialize = "T: serde::Serialize",
35 deserialize = "T: serde::Deserialize<'de>"
36 ))
37)]
38pub struct Rotation2D<T, Src, Dst> {
39 pub angle: T,
41 #[doc(hidden)]
42 pub _unit: PhantomData<(Src, Dst)>,
43}
44
45impl<T: Copy, Src, Dst> Copy for Rotation2D<T, Src, Dst> {}
46
47impl<T: Clone, Src, Dst> Clone for Rotation2D<T, Src, Dst> {
48 fn clone(&self) -> Self {
49 Rotation2D {
50 angle: self.angle.clone(),
51 _unit: PhantomData,
52 }
53 }
54}
55
56impl<T, Src, Dst> Eq for Rotation2D<T, Src, Dst> where T: Eq {}
57
58impl<T, Src, Dst> PartialEq for Rotation2D<T, Src, Dst>
59where
60 T: PartialEq,
61{
62 fn eq(&self, other: &Self) -> bool {
63 self.angle == other.angle
64 }
65}
66
67impl<T, Src, Dst> Hash for Rotation2D<T, Src, Dst>
68where
69 T: Hash,
70{
71 fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
72 self.angle.hash(h);
73 }
74}
75
76#[cfg(feature = "arbitrary")]
77impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Rotation2D<T, Src, Dst>
78where
79 T: arbitrary::Arbitrary<'a>,
80{
81 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
82 Ok(Rotation2D::new(arbitrary::Arbitrary::arbitrary(u)?))
83 }
84}
85
86#[cfg(feature = "bytemuck")]
87unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation2D<T, Src, Dst> {}
88
89#[cfg(feature = "bytemuck")]
90unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation2D<T, Src, Dst> {}
91
92impl<T, Src, Dst> Rotation2D<T, Src, Dst> {
93 #[inline]
95 pub fn new(angle: Angle<T>) -> Self {
96 Rotation2D {
97 angle: angle.radians,
98 _unit: PhantomData,
99 }
100 }
101
102 pub fn radians(angle: T) -> Self {
104 Self::new(Angle::radians(angle))
105 }
106
107 #[inline]
109 pub fn identity() -> Self
110 where
111 T: Zero,
112 {
113 Self::radians(T::zero())
114 }
115}
116
117impl<T: Copy, Src, Dst> Rotation2D<T, Src, Dst> {
118 #[inline]
135 pub fn cast_unit<Src2, Dst2>(&self) -> Rotation2D<T, Src2, Dst2> {
136 Rotation2D {
137 angle: self.angle,
138 _unit: PhantomData,
139 }
140 }
141
142 #[inline]
156 pub fn to_untyped(&self) -> Rotation2D<T, UnknownUnit, UnknownUnit> {
157 self.cast_unit()
158 }
159
160 #[inline]
175 pub fn from_untyped(r: &Rotation2D<T, UnknownUnit, UnknownUnit>) -> Self {
176 r.cast_unit()
177 }
178}
179
180impl<T, Src, Dst> Rotation2D<T, Src, Dst>
181where
182 T: Copy,
183{
184 pub fn get_angle(&self) -> Angle<T> {
186 Angle::radians(self.angle)
187 }
188}
189
190impl<T: Real, Src, Dst> Rotation2D<T, Src, Dst> {
191 #[inline]
193 pub fn to_3d(&self) -> Rotation3D<T, Src, Dst> {
194 Rotation3D::around_z(self.get_angle())
195 }
196
197 #[inline]
199 pub fn inverse(&self) -> Rotation2D<T, Dst, Src> {
200 Rotation2D::radians(-self.angle)
201 }
202
203 #[inline]
205 pub fn then<NewSrc>(&self, other: &Rotation2D<T, NewSrc, Src>) -> Rotation2D<T, NewSrc, Dst> {
206 Rotation2D::radians(self.angle + other.angle)
207 }
208
209 #[inline]
213 pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> {
214 let (sin, cos) = Real::sin_cos(self.angle);
215 point2(point.x * cos - point.y * sin, point.y * cos + point.x * sin)
216 }
217
218 #[inline]
222 pub fn transform_vector(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst> {
223 self.transform_point(vector.to_point()).to_vector()
224 }
225}
226
227impl<T, Src, Dst> Rotation2D<T, Src, Dst>
228where
229 T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
230{
231 #[inline]
233 pub fn to_transform(&self) -> Transform2D<T, Src, Dst> {
234 Transform2D::rotation(self.get_angle())
235 }
236}
237
238impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation2D<T, Src, Dst> {
239 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
240 write!(f, "Rotation({:?} rad)", self.angle)
241 }
242}
243
244impl<T, Src, Dst> ApproxEq<T> for Rotation2D<T, Src, Dst>
245where
246 T: Copy + Neg<Output = T> + ApproxEq<T>,
247{
248 fn approx_epsilon() -> T {
249 T::approx_epsilon()
250 }
251
252 fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
253 self.angle.approx_eq_eps(&other.angle, eps)
254 }
255}
256
257#[repr(C)]
270#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
271#[cfg_attr(
272 feature = "serde",
273 serde(bound(
274 serialize = "T: serde::Serialize",
275 deserialize = "T: serde::Deserialize<'de>"
276 ))
277)]
278pub struct Rotation3D<T, Src, Dst> {
279 pub i: T,
281 pub j: T,
283 pub k: T,
285 pub r: T,
287 #[doc(hidden)]
288 pub _unit: PhantomData<(Src, Dst)>,
289}
290
291impl<T: Copy, Src, Dst> Copy for Rotation3D<T, Src, Dst> {}
292
293impl<T: Clone, Src, Dst> Clone for Rotation3D<T, Src, Dst> {
294 fn clone(&self) -> Self {
295 Rotation3D {
296 i: self.i.clone(),
297 j: self.j.clone(),
298 k: self.k.clone(),
299 r: self.r.clone(),
300 _unit: PhantomData,
301 }
302 }
303}
304
305impl<T, Src, Dst> Eq for Rotation3D<T, Src, Dst> where T: Eq {}
306
307impl<T, Src, Dst> PartialEq for Rotation3D<T, Src, Dst>
308where
309 T: PartialEq,
310{
311 fn eq(&self, other: &Self) -> bool {
312 self.i == other.i && self.j == other.j && self.k == other.k && self.r == other.r
313 }
314}
315
316impl<T, Src, Dst> Hash for Rotation3D<T, Src, Dst>
317where
318 T: Hash,
319{
320 fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
321 self.i.hash(h);
322 self.j.hash(h);
323 self.k.hash(h);
324 self.r.hash(h);
325 }
326}
327
328#[cfg(feature = "arbitrary")]
332impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Rotation3D<T, Src, Dst>
333where
334 T: arbitrary::Arbitrary<'a>,
335{
336 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
337 let (i, j, k, r) = arbitrary::Arbitrary::arbitrary(u)?;
338 Ok(Rotation3D::quaternion(i, j, k, r))
339 }
340}
341
342#[cfg(feature = "bytemuck")]
343unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation3D<T, Src, Dst> {}
344
345#[cfg(feature = "bytemuck")]
346unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation3D<T, Src, Dst> {}
347
348impl<T, Src, Dst> Rotation3D<T, Src, Dst> {
349 #[inline]
359 pub fn quaternion(a: T, b: T, c: T, r: T) -> Self {
360 Rotation3D {
361 i: a,
362 j: b,
363 k: c,
364 r,
365 _unit: PhantomData,
366 }
367 }
368
369 #[inline]
371 pub fn identity() -> Self
372 where
373 T: Zero + One,
374 {
375 Self::quaternion(T::zero(), T::zero(), T::zero(), T::one())
376 }
377}
378
379impl<T, Src, Dst> Rotation3D<T, Src, Dst>
380where
381 T: Copy,
382{
383 #[inline]
385 pub fn vector_part(&self) -> Vector3D<T, UnknownUnit> {
386 vec3(self.i, self.j, self.k)
387 }
388
389 #[inline]
409 pub fn cast_unit<Src2, Dst2>(&self) -> Rotation3D<T, Src2, Dst2> {
410 Rotation3D {
411 i: self.i,
412 j: self.j,
413 k: self.k,
414 r: self.r,
415 _unit: PhantomData,
416 }
417 }
418
419 #[inline]
436 pub fn to_untyped(&self) -> Rotation3D<T, UnknownUnit, UnknownUnit> {
437 self.cast_unit()
438 }
439
440 #[inline]
458 pub fn from_untyped(r: &Rotation3D<T, UnknownUnit, UnknownUnit>) -> Self {
459 r.cast_unit()
460 }
461}
462
463impl<T, Src, Dst> Rotation3D<T, Src, Dst>
464where
465 T: Real,
466{
467 #[inline]
473 pub fn unit_quaternion(i: T, j: T, k: T, r: T) -> Self {
474 Self::quaternion(i, j, k, r).normalize()
475 }
476
477 pub fn around_axis(axis: Vector3D<T, Src>, angle: Angle<T>) -> Self {
479 let axis = axis.normalize();
480 let two = T::one() + T::one();
481 let (sin, cos) = Angle::sin_cos(angle / two);
482 Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos)
483 }
484
485 pub fn around_x(angle: Angle<T>) -> Self {
487 let zero = Zero::zero();
488 let two = T::one() + T::one();
489 let (sin, cos) = Angle::sin_cos(angle / two);
490 Self::quaternion(sin, zero, zero, cos)
491 }
492
493 pub fn around_y(angle: Angle<T>) -> Self {
495 let zero = Zero::zero();
496 let two = T::one() + T::one();
497 let (sin, cos) = Angle::sin_cos(angle / two);
498 Self::quaternion(zero, sin, zero, cos)
499 }
500
501 pub fn around_z(angle: Angle<T>) -> Self {
503 let zero = Zero::zero();
504 let two = T::one() + T::one();
505 let (sin, cos) = Angle::sin_cos(angle / two);
506 Self::quaternion(zero, zero, sin, cos)
507 }
508
509 pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self {
517 let half = T::one() / (T::one() + T::one());
518
519 let (sy, cy) = Real::sin_cos(half * yaw.get());
520 let (sp, cp) = Real::sin_cos(half * pitch.get());
521 let (sr, cr) = Real::sin_cos(half * roll.get());
522
523 Self::quaternion(
524 cy * sr * cp - sy * cr * sp,
525 cy * cr * sp + sy * sr * cp,
526 sy * cr * cp - cy * sr * sp,
527 cy * cr * cp + sy * sr * sp,
528 )
529 }
530
531 #[inline]
533 pub fn inverse(&self) -> Rotation3D<T, Dst, Src> {
534 Rotation3D::quaternion(-self.i, -self.j, -self.k, self.r)
535 }
536
537 #[inline]
539 pub fn norm(&self) -> T {
540 self.square_norm().sqrt()
541 }
542
543 #[inline]
545 pub fn square_norm(&self) -> T {
546 self.i * self.i + self.j * self.j + self.k * self.k + self.r * self.r
547 }
548
549 #[inline]
553 pub fn normalize(&self) -> Self {
554 self.mul(T::one() / self.norm())
555 }
556
557 #[inline]
561 pub fn is_normalized(&self) -> bool
562 where
563 T: ApproxEq<T>,
564 {
565 let eps = NumCast::from(1.0e-5).unwrap();
566 self.square_norm().approx_eq_eps(&T::one(), &eps)
567 }
568
569 pub fn slerp(&self, other: &Self, t: T) -> Self
573 where
574 T: ApproxEq<T>,
575 {
576 debug_assert!(self.is_normalized());
577 debug_assert!(other.is_normalized());
578
579 let r1 = *self;
580 let mut r2 = *other;
581
582 let mut dot = r1.i * r2.i + r1.j * r2.j + r1.k * r2.k + r1.r * r2.r;
583
584 let one = T::one();
585
586 if dot.approx_eq(&T::one()) {
587 return r1.lerp(&r2, t);
589 }
590
591 if dot < T::zero() {
595 r2 = r2.mul(-T::one());
596 dot = -dot;
597 }
598
599 dot = Real::min(dot, one);
601
602 let theta = Real::acos(dot) * t;
604
605 let r3 = r2.sub(r1.mul(dot)).normalize();
607 let (sin, cos) = Real::sin_cos(theta);
608 r1.mul(cos).add(r3.mul(sin))
609 }
610
611 #[inline]
613 pub fn lerp(&self, other: &Self, t: T) -> Self {
614 let one_t = T::one() - t;
615 self.mul(one_t).add(other.mul(t)).normalize()
616 }
617
618 pub fn transform_point3d(&self, point: Point3D<T, Src>) -> Point3D<T, Dst>
622 where
623 T: ApproxEq<T>,
624 {
625 debug_assert!(self.is_normalized());
626
627 let two = T::one() + T::one();
628 let cross = self.vector_part().cross(point.to_vector().to_untyped()) * two;
629
630 point3(
631 point.x + self.r * cross.x + self.j * cross.z - self.k * cross.y,
632 point.y + self.r * cross.y + self.k * cross.x - self.i * cross.z,
633 point.z + self.r * cross.z + self.i * cross.y - self.j * cross.x,
634 )
635 }
636
637 #[inline]
641 pub fn transform_point2d(&self, point: Point2D<T, Src>) -> Point2D<T, Dst>
642 where
643 T: ApproxEq<T>,
644 {
645 self.transform_point3d(point.to_3d()).xy()
646 }
647
648 #[inline]
652 pub fn transform_vector3d(&self, vector: Vector3D<T, Src>) -> Vector3D<T, Dst>
653 where
654 T: ApproxEq<T>,
655 {
656 self.transform_point3d(vector.to_point()).to_vector()
657 }
658
659 #[inline]
663 pub fn transform_vector2d(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst>
664 where
665 T: ApproxEq<T>,
666 {
667 self.transform_vector3d(vector.to_3d()).xy()
668 }
669
670 #[inline]
672 #[rustfmt::skip]
673 pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
674 where
675 T: ApproxEq<T>,
676 {
677 debug_assert!(self.is_normalized());
678
679 let i2 = self.i + self.i;
680 let j2 = self.j + self.j;
681 let k2 = self.k + self.k;
682 let ii = self.i * i2;
683 let ij = self.i * j2;
684 let ik = self.i * k2;
685 let jj = self.j * j2;
686 let jk = self.j * k2;
687 let kk = self.k * k2;
688 let ri = self.r * i2;
689 let rj = self.r * j2;
690 let rk = self.r * k2;
691
692 let one = T::one();
693 let zero = T::zero();
694
695 let m11 = one - (jj + kk);
696 let m12 = ij + rk;
697 let m13 = ik - rj;
698
699 let m21 = ij - rk;
700 let m22 = one - (ii + kk);
701 let m23 = jk + ri;
702
703 let m31 = ik + rj;
704 let m32 = jk - ri;
705 let m33 = one - (ii + jj);
706
707 Transform3D::new(
708 m11, m12, m13, zero,
709 m21, m22, m23, zero,
710 m31, m32, m33, zero,
711 zero, zero, zero, one,
712 )
713 }
714
715 #[inline]
717 pub fn then<NewDst>(&self, other: &Rotation3D<T, Dst, NewDst>) -> Rotation3D<T, Src, NewDst>
718 where
719 T: ApproxEq<T>,
720 {
721 debug_assert!(self.is_normalized());
722 Rotation3D::quaternion(
723 other.i * self.r + other.r * self.i + other.j * self.k - other.k * self.j,
724 other.j * self.r + other.r * self.j + other.k * self.i - other.i * self.k,
725 other.k * self.r + other.r * self.k + other.i * self.j - other.j * self.i,
726 other.r * self.r - other.i * self.i - other.j * self.j - other.k * self.k,
727 )
728 }
729
730 #[inline]
734 fn add(&self, other: Self) -> Self {
735 Self::quaternion(
736 self.i + other.i,
737 self.j + other.j,
738 self.k + other.k,
739 self.r + other.r,
740 )
741 }
742
743 #[inline]
744 fn sub(&self, other: Self) -> Self {
745 Self::quaternion(
746 self.i - other.i,
747 self.j - other.j,
748 self.k - other.k,
749 self.r - other.r,
750 )
751 }
752
753 #[inline]
754 fn mul(&self, factor: T) -> Self {
755 Self::quaternion(
756 self.i * factor,
757 self.j * factor,
758 self.k * factor,
759 self.r * factor,
760 )
761 }
762}
763
764impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation3D<T, Src, Dst> {
765 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
766 write!(
767 f,
768 "Quat({:?}*i + {:?}*j + {:?}*k + {:?})",
769 self.i, self.j, self.k, self.r
770 )
771 }
772}
773
774impl<T, Src, Dst> ApproxEq<T> for Rotation3D<T, Src, Dst>
775where
776 T: Copy + Neg<Output = T> + ApproxEq<T>,
777{
778 fn approx_epsilon() -> T {
779 T::approx_epsilon()
780 }
781
782 fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
783 (self.i.approx_eq_eps(&other.i, eps)
784 && self.j.approx_eq_eps(&other.j, eps)
785 && self.k.approx_eq_eps(&other.k, eps)
786 && self.r.approx_eq_eps(&other.r, eps))
787 || (self.i.approx_eq_eps(&-other.i, eps)
788 && self.j.approx_eq_eps(&-other.j, eps)
789 && self.k.approx_eq_eps(&-other.k, eps)
790 && self.r.approx_eq_eps(&-other.r, eps))
791 }
792}
793
794#[test]
795fn simple_rotation_2d() {
796 use crate::default::Rotation2D;
797 use core::f32::consts::{FRAC_PI_2, PI};
798
799 let ri = Rotation2D::identity();
800 let r90 = Rotation2D::radians(FRAC_PI_2);
801 let rm90 = Rotation2D::radians(-FRAC_PI_2);
802 let r180 = Rotation2D::radians(PI);
803
804 assert!(ri
805 .transform_point(point2(1.0, 2.0))
806 .approx_eq(&point2(1.0, 2.0)));
807 assert!(r90
808 .transform_point(point2(1.0, 2.0))
809 .approx_eq(&point2(-2.0, 1.0)));
810 assert!(rm90
811 .transform_point(point2(1.0, 2.0))
812 .approx_eq(&point2(2.0, -1.0)));
813 assert!(r180
814 .transform_point(point2(1.0, 2.0))
815 .approx_eq(&point2(-1.0, -2.0)));
816
817 assert!(r90
818 .inverse()
819 .inverse()
820 .transform_point(point2(1.0, 2.0))
821 .approx_eq(&r90.transform_point(point2(1.0, 2.0))));
822}
823
824#[test]
825fn simple_rotation_3d_in_2d() {
826 use crate::default::Rotation3D;
827 use core::f32::consts::{FRAC_PI_2, PI};
828
829 let ri = Rotation3D::identity();
830 let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
831 let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2));
832 let r180 = Rotation3D::around_z(Angle::radians(PI));
833
834 assert!(ri
835 .transform_point2d(point2(1.0, 2.0))
836 .approx_eq(&point2(1.0, 2.0)));
837 assert!(r90
838 .transform_point2d(point2(1.0, 2.0))
839 .approx_eq(&point2(-2.0, 1.0)));
840 assert!(rm90
841 .transform_point2d(point2(1.0, 2.0))
842 .approx_eq(&point2(2.0, -1.0)));
843 assert!(r180
844 .transform_point2d(point2(1.0, 2.0))
845 .approx_eq(&point2(-1.0, -2.0)));
846
847 assert!(r90
848 .inverse()
849 .inverse()
850 .transform_point2d(point2(1.0, 2.0))
851 .approx_eq(&r90.transform_point2d(point2(1.0, 2.0))));
852}
853
854#[test]
855fn pre_post() {
856 use crate::default::Rotation3D;
857 use core::f32::consts::FRAC_PI_2;
858
859 let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2));
860 let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2));
861 let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
862
863 let t1 = r1.to_transform();
864 let t2 = r2.to_transform();
865 let t3 = r3.to_transform();
866
867 let p = point3(1.0, 2.0, 3.0);
868
869 let p1 = r1.then(&r2).then(&r3).transform_point3d(p);
872 let p2 = t1.then(&t2).then(&t3).transform_point3d(p);
873
874 assert!(p1.approx_eq(&p2.unwrap()));
875
876 let p3 = t3.then(&t1).then(&t2).transform_point3d(p);
878 assert!(!p1.approx_eq(&p3.unwrap()));
879}
880
881#[test]
882fn to_transform3d() {
883 use crate::default::Rotation3D;
884
885 use core::f32::consts::{FRAC_PI_2, PI};
886 let rotations = [
887 Rotation3D::identity(),
888 Rotation3D::around_x(Angle::radians(FRAC_PI_2)),
889 Rotation3D::around_x(Angle::radians(-FRAC_PI_2)),
890 Rotation3D::around_x(Angle::radians(PI)),
891 Rotation3D::around_y(Angle::radians(FRAC_PI_2)),
892 Rotation3D::around_y(Angle::radians(-FRAC_PI_2)),
893 Rotation3D::around_y(Angle::radians(PI)),
894 Rotation3D::around_z(Angle::radians(FRAC_PI_2)),
895 Rotation3D::around_z(Angle::radians(-FRAC_PI_2)),
896 Rotation3D::around_z(Angle::radians(PI)),
897 ];
898
899 let points = [
900 point3(0.0, 0.0, 0.0),
901 point3(1.0, 2.0, 3.0),
902 point3(-5.0, 3.0, -1.0),
903 point3(-0.5, -1.0, 1.5),
904 ];
905
906 for rotation in &rotations {
907 for &point in &points {
908 let p1 = rotation.transform_point3d(point);
909 let p2 = rotation.to_transform().transform_point3d(point);
910 assert!(p1.approx_eq(&p2.unwrap()));
911 }
912 }
913}
914
915#[test]
916fn slerp() {
917 use crate::default::Rotation3D;
918
919 let q1 = Rotation3D::quaternion(1.0, 0.0, 0.0, 0.0);
920 let q2 = Rotation3D::quaternion(0.0, 1.0, 0.0, 0.0);
921 let q3 = Rotation3D::quaternion(0.0, 0.0, -1.0, 0.0);
922
923 assert!(q1.slerp(&q2, 0.0).approx_eq(&q1));
931 assert!(q1.slerp(&q2, 0.2).approx_eq(&Rotation3D::quaternion(
932 0.951056516295154,
933 0.309016994374947,
934 0.0,
935 0.0
936 )));
937 assert!(q1.slerp(&q2, 0.4).approx_eq(&Rotation3D::quaternion(
938 0.809016994374947,
939 0.587785252292473,
940 0.0,
941 0.0
942 )));
943 assert!(q1.slerp(&q2, 0.6).approx_eq(&Rotation3D::quaternion(
944 0.587785252292473,
945 0.809016994374947,
946 0.0,
947 0.0
948 )));
949 assert!(q1.slerp(&q2, 0.8).approx_eq(&Rotation3D::quaternion(
950 0.309016994374947,
951 0.951056516295154,
952 0.0,
953 0.0
954 )));
955 assert!(q1.slerp(&q2, 1.0).approx_eq(&q2));
956
957 assert!(q1.slerp(&q3, 0.0).approx_eq(&q1));
958 assert!(q1.slerp(&q3, 0.2).approx_eq(&Rotation3D::quaternion(
959 0.951056516295154,
960 0.0,
961 -0.309016994374947,
962 0.0
963 )));
964 assert!(q1.slerp(&q3, 0.4).approx_eq(&Rotation3D::quaternion(
965 0.809016994374947,
966 0.0,
967 -0.587785252292473,
968 0.0
969 )));
970 assert!(q1.slerp(&q3, 0.6).approx_eq(&Rotation3D::quaternion(
971 0.587785252292473,
972 0.0,
973 -0.809016994374947,
974 0.0
975 )));
976 assert!(q1.slerp(&q3, 0.8).approx_eq(&Rotation3D::quaternion(
977 0.309016994374947,
978 0.0,
979 -0.951056516295154,
980 0.0
981 )));
982 assert!(q1.slerp(&q3, 1.0).approx_eq(&q3));
983}
984
985#[test]
986fn around_axis() {
987 use crate::default::Rotation3D;
988 use core::f32::consts::{FRAC_PI_2, PI};
989
990 let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI));
992 let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2));
993 assert!(r1
994 .transform_point3d(point3(1.0, 2.0, 0.0))
995 .approx_eq(&point3(2.0, 1.0, 0.0)));
996 assert!(r2
997 .transform_point3d(point3(1.0, 0.0, 0.0))
998 .approx_eq(&point3(0.5, 0.5, -0.5.sqrt())));
999
1000 let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288));
1002 assert!(r3
1003 .transform_point3d(point3(1.0, 0.0, 0.0))
1004 .approx_eq(&point3(-0.58071821, 0.81401868, -0.01182979)));
1005}
1006
1007#[test]
1008fn from_euler() {
1009 use crate::default::Rotation3D;
1010 use core::f32::consts::FRAC_PI_2;
1011
1012 let p = point3(1.0, 2.0, 3.0);
1017
1018 let angle = Angle::radians(FRAC_PI_2);
1019 let zero = Angle::radians(0.0);
1020
1021 let roll_re = Rotation3D::euler(angle, zero, zero);
1023 let roll_rq = Rotation3D::around_x(angle);
1024 let roll_pe = roll_re.transform_point3d(p);
1025 let roll_pq = roll_rq.transform_point3d(p);
1026
1027 let pitch_re = Rotation3D::euler(zero, angle, zero);
1029 let pitch_rq = Rotation3D::around_y(angle);
1030 let pitch_pe = pitch_re.transform_point3d(p);
1031 let pitch_pq = pitch_rq.transform_point3d(p);
1032
1033 let yaw_re = Rotation3D::euler(zero, zero, angle);
1035 let yaw_rq = Rotation3D::around_z(angle);
1036 let yaw_pe = yaw_re.transform_point3d(p);
1037 let yaw_pq = yaw_rq.transform_point3d(p);
1038
1039 assert!(roll_pe.approx_eq(&roll_pq));
1040 assert!(pitch_pe.approx_eq(&pitch_pq));
1041 assert!(yaw_pe.approx_eq(&yaw_pq));
1042
1043 let ypr_e = Rotation3D::euler(angle, angle, angle);
1046 let ypr_q = roll_rq.then(&pitch_rq).then(&yaw_rq);
1047 let ypr_pe = ypr_e.transform_point3d(p);
1048 let ypr_pq = ypr_q.transform_point3d(p);
1049
1050 assert!(ypr_pe.approx_eq(&ypr_pq));
1051}