1use crate::approxeq::ApproxEq;
11use crate::num::{One, Zero};
12use crate::trig::Trig;
13use crate::{point2, point3, vec3, Angle, Point2D, Point3D, Vector2D, Vector3D};
14use crate::{Transform2D, Transform3D, UnknownUnit};
15
16use core::cmp::{Eq, PartialEq};
17use core::fmt;
18use core::hash::Hash;
19use core::marker::PhantomData;
20use core::ops::{Add, Mul, Neg, Sub};
21
22#[cfg(feature = "bytemuck")]
23use bytemuck::{Pod, Zeroable};
24#[cfg(feature = "malloc_size_of")]
25use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
26use num_traits::real::Real;
27use num_traits::NumCast;
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31#[repr(C)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[cfg_attr(
35 feature = "serde",
36 serde(bound(
37 serialize = "T: serde::Serialize",
38 deserialize = "T: serde::Deserialize<'de>"
39 ))
40)]
41pub struct Rotation2D<T, Src, Dst> {
42 pub angle: T,
44 #[doc(hidden)]
45 pub _unit: PhantomData<(Src, Dst)>,
46}
47
48impl<T: Copy, Src, Dst> Copy for Rotation2D<T, Src, Dst> {}
49
50impl<T: Clone, Src, Dst> Clone for Rotation2D<T, Src, Dst> {
51 fn clone(&self) -> Self {
52 Rotation2D {
53 angle: self.angle.clone(),
54 _unit: PhantomData,
55 }
56 }
57}
58
59impl<T, Src, Dst> Eq for Rotation2D<T, Src, Dst> where T: Eq {}
60
61impl<T, Src, Dst> PartialEq for Rotation2D<T, Src, Dst>
62where
63 T: PartialEq,
64{
65 fn eq(&self, other: &Self) -> bool {
66 self.angle == other.angle
67 }
68}
69
70impl<T, Src, Dst> Hash for Rotation2D<T, Src, Dst>
71where
72 T: Hash,
73{
74 fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
75 self.angle.hash(h);
76 }
77}
78
79#[cfg(feature = "arbitrary")]
80impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Rotation2D<T, Src, Dst>
81where
82 T: arbitrary::Arbitrary<'a>,
83{
84 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
85 Ok(Rotation2D::new(arbitrary::Arbitrary::arbitrary(u)?))
86 }
87}
88
89#[cfg(feature = "bytemuck")]
90unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation2D<T, Src, Dst> {}
91
92#[cfg(feature = "bytemuck")]
93unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation2D<T, Src, Dst> {}
94
95#[cfg(feature = "malloc_size_of")]
96impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for Rotation2D<T, Src, Dst> {
97 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
98 self.angle.size_of(ops)
99 }
100}
101
102impl<T, Src, Dst> Rotation2D<T, Src, Dst> {
103 #[inline]
105 pub fn new(angle: Angle<T>) -> Self {
106 Rotation2D {
107 angle: angle.radians,
108 _unit: PhantomData,
109 }
110 }
111
112 pub fn radians(angle: T) -> Self {
114 Self::new(Angle::radians(angle))
115 }
116
117 #[inline]
119 pub fn identity() -> Self
120 where
121 T: Zero,
122 {
123 Self::radians(T::zero())
124 }
125}
126
127impl<T: Copy, Src, Dst> Rotation2D<T, Src, Dst> {
128 #[inline]
145 pub fn cast_unit<Src2, Dst2>(&self) -> Rotation2D<T, Src2, Dst2> {
146 Rotation2D {
147 angle: self.angle,
148 _unit: PhantomData,
149 }
150 }
151
152 #[inline]
166 pub fn to_untyped(&self) -> Rotation2D<T, UnknownUnit, UnknownUnit> {
167 self.cast_unit()
168 }
169
170 #[inline]
185 pub fn from_untyped(r: &Rotation2D<T, UnknownUnit, UnknownUnit>) -> Self {
186 r.cast_unit()
187 }
188}
189
190impl<T, Src, Dst> Rotation2D<T, Src, Dst>
191where
192 T: Copy,
193{
194 pub fn get_angle(&self) -> Angle<T> {
196 Angle::radians(self.angle)
197 }
198}
199
200impl<T: Real, Src, Dst> Rotation2D<T, Src, Dst> {
201 #[inline]
203 pub fn to_3d(&self) -> Rotation3D<T, Src, Dst> {
204 Rotation3D::around_z(self.get_angle())
205 }
206
207 #[inline]
209 pub fn inverse(&self) -> Rotation2D<T, Dst, Src> {
210 Rotation2D::radians(-self.angle)
211 }
212
213 #[inline]
215 pub fn then<NewSrc>(&self, other: &Rotation2D<T, NewSrc, Src>) -> Rotation2D<T, NewSrc, Dst> {
216 Rotation2D::radians(self.angle + other.angle)
217 }
218
219 #[inline]
223 pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> {
224 let (sin, cos) = Real::sin_cos(self.angle);
225 point2(point.x * cos - point.y * sin, point.y * cos + point.x * sin)
226 }
227
228 #[inline]
232 pub fn transform_vector(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst> {
233 self.transform_point(vector.to_point()).to_vector()
234 }
235}
236
237impl<T, Src, Dst> Rotation2D<T, Src, Dst>
238where
239 T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
240{
241 #[inline]
243 pub fn to_transform(&self) -> Transform2D<T, Src, Dst> {
244 Transform2D::rotation(self.get_angle())
245 }
246}
247
248impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation2D<T, Src, Dst> {
249 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250 write!(f, "Rotation({:?} rad)", self.angle)
251 }
252}
253
254impl<T, Src, Dst> ApproxEq<T> for Rotation2D<T, Src, Dst>
255where
256 T: Copy + Neg<Output = T> + ApproxEq<T>,
257{
258 fn approx_epsilon() -> T {
259 T::approx_epsilon()
260 }
261
262 fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
263 self.angle.approx_eq_eps(&other.angle, eps)
264 }
265}
266
267#[repr(C)]
280#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
281#[cfg_attr(
282 feature = "serde",
283 serde(bound(
284 serialize = "T: serde::Serialize",
285 deserialize = "T: serde::Deserialize<'de>"
286 ))
287)]
288pub struct Rotation3D<T, Src, Dst> {
289 pub i: T,
291 pub j: T,
293 pub k: T,
295 pub r: T,
297 #[doc(hidden)]
298 pub _unit: PhantomData<(Src, Dst)>,
299}
300
301impl<T: Copy, Src, Dst> Copy for Rotation3D<T, Src, Dst> {}
302
303impl<T: Clone, Src, Dst> Clone for Rotation3D<T, Src, Dst> {
304 fn clone(&self) -> Self {
305 Rotation3D {
306 i: self.i.clone(),
307 j: self.j.clone(),
308 k: self.k.clone(),
309 r: self.r.clone(),
310 _unit: PhantomData,
311 }
312 }
313}
314
315impl<T, Src, Dst> Eq for Rotation3D<T, Src, Dst> where T: Eq {}
316
317impl<T, Src, Dst> PartialEq for Rotation3D<T, Src, Dst>
318where
319 T: PartialEq,
320{
321 fn eq(&self, other: &Self) -> bool {
322 self.i == other.i && self.j == other.j && self.k == other.k && self.r == other.r
323 }
324}
325
326impl<T, Src, Dst> Hash for Rotation3D<T, Src, Dst>
327where
328 T: Hash,
329{
330 fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
331 self.i.hash(h);
332 self.j.hash(h);
333 self.k.hash(h);
334 self.r.hash(h);
335 }
336}
337
338#[cfg(feature = "arbitrary")]
342impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Rotation3D<T, Src, Dst>
343where
344 T: arbitrary::Arbitrary<'a>,
345{
346 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
347 let (i, j, k, r) = arbitrary::Arbitrary::arbitrary(u)?;
348 Ok(Rotation3D::quaternion(i, j, k, r))
349 }
350}
351
352#[cfg(feature = "bytemuck")]
353unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation3D<T, Src, Dst> {}
354
355#[cfg(feature = "bytemuck")]
356unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation3D<T, Src, Dst> {}
357
358#[cfg(feature = "malloc_size_of")]
359impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for Rotation3D<T, Src, Dst> {
360 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
361 self.i.size_of(ops) + self.j.size_of(ops) + self.k.size_of(ops) + self.r.size_of(ops)
362 }
363}
364
365impl<T, Src, Dst> Rotation3D<T, Src, Dst> {
366 #[inline]
376 pub fn quaternion(a: T, b: T, c: T, r: T) -> Self {
377 Rotation3D {
378 i: a,
379 j: b,
380 k: c,
381 r,
382 _unit: PhantomData,
383 }
384 }
385
386 #[inline]
388 pub fn identity() -> Self
389 where
390 T: Zero + One,
391 {
392 Self::quaternion(T::zero(), T::zero(), T::zero(), T::one())
393 }
394}
395
396impl<T, Src, Dst> Rotation3D<T, Src, Dst>
397where
398 T: Copy,
399{
400 #[inline]
402 pub fn vector_part(&self) -> Vector3D<T, UnknownUnit> {
403 vec3(self.i, self.j, self.k)
404 }
405
406 #[inline]
426 pub fn cast_unit<Src2, Dst2>(&self) -> Rotation3D<T, Src2, Dst2> {
427 Rotation3D {
428 i: self.i,
429 j: self.j,
430 k: self.k,
431 r: self.r,
432 _unit: PhantomData,
433 }
434 }
435
436 #[inline]
453 pub fn to_untyped(&self) -> Rotation3D<T, UnknownUnit, UnknownUnit> {
454 self.cast_unit()
455 }
456
457 #[inline]
475 pub fn from_untyped(r: &Rotation3D<T, UnknownUnit, UnknownUnit>) -> Self {
476 r.cast_unit()
477 }
478}
479
480impl<T, Src, Dst> Rotation3D<T, Src, Dst>
481where
482 T: Real,
483{
484 #[inline]
490 pub fn unit_quaternion(i: T, j: T, k: T, r: T) -> Self {
491 Self::quaternion(i, j, k, r).normalize()
492 }
493
494 pub fn around_axis(axis: Vector3D<T, Src>, angle: Angle<T>) -> Self {
496 let axis = axis.normalize();
497 let two = T::one() + T::one();
498 let (sin, cos) = Angle::sin_cos(angle / two);
499 Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos)
500 }
501
502 pub fn around_x(angle: Angle<T>) -> Self {
504 let zero = Zero::zero();
505 let two = T::one() + T::one();
506 let (sin, cos) = Angle::sin_cos(angle / two);
507 Self::quaternion(sin, zero, zero, cos)
508 }
509
510 pub fn around_y(angle: Angle<T>) -> Self {
512 let zero = Zero::zero();
513 let two = T::one() + T::one();
514 let (sin, cos) = Angle::sin_cos(angle / two);
515 Self::quaternion(zero, sin, zero, cos)
516 }
517
518 pub fn around_z(angle: Angle<T>) -> Self {
520 let zero = Zero::zero();
521 let two = T::one() + T::one();
522 let (sin, cos) = Angle::sin_cos(angle / two);
523 Self::quaternion(zero, zero, sin, cos)
524 }
525
526 pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self {
534 let half = T::one() / (T::one() + T::one());
535
536 let (sy, cy) = Real::sin_cos(half * yaw.get());
537 let (sp, cp) = Real::sin_cos(half * pitch.get());
538 let (sr, cr) = Real::sin_cos(half * roll.get());
539
540 Self::quaternion(
541 cy * sr * cp - sy * cr * sp,
542 cy * cr * sp + sy * sr * cp,
543 sy * cr * cp - cy * sr * sp,
544 cy * cr * cp + sy * sr * sp,
545 )
546 }
547
548 #[inline]
550 pub fn inverse(&self) -> Rotation3D<T, Dst, Src> {
551 Rotation3D::quaternion(-self.i, -self.j, -self.k, self.r)
552 }
553
554 #[inline]
556 pub fn norm(&self) -> T {
557 self.square_norm().sqrt()
558 }
559
560 #[inline]
562 pub fn square_norm(&self) -> T {
563 self.i * self.i + self.j * self.j + self.k * self.k + self.r * self.r
564 }
565
566 #[inline]
570 pub fn normalize(&self) -> Self {
571 self.mul(T::one() / self.norm())
572 }
573
574 #[inline]
578 pub fn is_normalized(&self) -> bool
579 where
580 T: ApproxEq<T>,
581 {
582 let eps = NumCast::from(1.0e-5).unwrap();
583 self.square_norm().approx_eq_eps(&T::one(), &eps)
584 }
585
586 pub fn slerp(&self, other: &Self, t: T) -> Self
590 where
591 T: ApproxEq<T>,
592 {
593 debug_assert!(self.is_normalized());
594 debug_assert!(other.is_normalized());
595
596 let r1 = *self;
597 let mut r2 = *other;
598
599 let mut dot = r1.i * r2.i + r1.j * r2.j + r1.k * r2.k + r1.r * r2.r;
600
601 let one = T::one();
602
603 if dot.approx_eq(&T::one()) {
604 return r1.lerp(&r2, t);
606 }
607
608 if dot < T::zero() {
612 r2 = r2.mul(-T::one());
613 dot = -dot;
614 }
615
616 dot = Real::min(dot, one);
618
619 let theta = Real::acos(dot) * t;
621
622 let r3 = r2.sub(r1.mul(dot)).normalize();
624 let (sin, cos) = Real::sin_cos(theta);
625 r1.mul(cos).add(r3.mul(sin))
626 }
627
628 #[inline]
630 pub fn lerp(&self, other: &Self, t: T) -> Self {
631 let one_t = T::one() - t;
632 self.mul(one_t).add(other.mul(t)).normalize()
633 }
634
635 pub fn transform_point3d(&self, point: Point3D<T, Src>) -> Point3D<T, Dst>
639 where
640 T: ApproxEq<T>,
641 {
642 debug_assert!(self.is_normalized());
643
644 let two = T::one() + T::one();
645 let cross = self.vector_part().cross(point.to_vector().to_untyped()) * two;
646
647 point3(
648 point.x + self.r * cross.x + self.j * cross.z - self.k * cross.y,
649 point.y + self.r * cross.y + self.k * cross.x - self.i * cross.z,
650 point.z + self.r * cross.z + self.i * cross.y - self.j * cross.x,
651 )
652 }
653
654 #[inline]
658 pub fn transform_point2d(&self, point: Point2D<T, Src>) -> Point2D<T, Dst>
659 where
660 T: ApproxEq<T>,
661 {
662 self.transform_point3d(point.to_3d()).xy()
663 }
664
665 #[inline]
669 pub fn transform_vector3d(&self, vector: Vector3D<T, Src>) -> Vector3D<T, Dst>
670 where
671 T: ApproxEq<T>,
672 {
673 self.transform_point3d(vector.to_point()).to_vector()
674 }
675
676 #[inline]
680 pub fn transform_vector2d(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst>
681 where
682 T: ApproxEq<T>,
683 {
684 self.transform_vector3d(vector.to_3d()).xy()
685 }
686
687 #[inline]
689 #[rustfmt::skip]
690 pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
691 where
692 T: ApproxEq<T>,
693 {
694 debug_assert!(self.is_normalized());
695
696 let i2 = self.i + self.i;
697 let j2 = self.j + self.j;
698 let k2 = self.k + self.k;
699 let ii = self.i * i2;
700 let ij = self.i * j2;
701 let ik = self.i * k2;
702 let jj = self.j * j2;
703 let jk = self.j * k2;
704 let kk = self.k * k2;
705 let ri = self.r * i2;
706 let rj = self.r * j2;
707 let rk = self.r * k2;
708
709 let one = T::one();
710 let zero = T::zero();
711
712 let m11 = one - (jj + kk);
713 let m12 = ij + rk;
714 let m13 = ik - rj;
715
716 let m21 = ij - rk;
717 let m22 = one - (ii + kk);
718 let m23 = jk + ri;
719
720 let m31 = ik + rj;
721 let m32 = jk - ri;
722 let m33 = one - (ii + jj);
723
724 Transform3D::new(
725 m11, m12, m13, zero,
726 m21, m22, m23, zero,
727 m31, m32, m33, zero,
728 zero, zero, zero, one,
729 )
730 }
731
732 #[inline]
734 pub fn then<NewDst>(&self, other: &Rotation3D<T, Dst, NewDst>) -> Rotation3D<T, Src, NewDst>
735 where
736 T: ApproxEq<T>,
737 {
738 debug_assert!(self.is_normalized());
739 Rotation3D::quaternion(
740 other.i * self.r + other.r * self.i + other.j * self.k - other.k * self.j,
741 other.j * self.r + other.r * self.j + other.k * self.i - other.i * self.k,
742 other.k * self.r + other.r * self.k + other.i * self.j - other.j * self.i,
743 other.r * self.r - other.i * self.i - other.j * self.j - other.k * self.k,
744 )
745 }
746
747 #[inline]
751 fn add(&self, other: Self) -> Self {
752 Self::quaternion(
753 self.i + other.i,
754 self.j + other.j,
755 self.k + other.k,
756 self.r + other.r,
757 )
758 }
759
760 #[inline]
761 fn sub(&self, other: Self) -> Self {
762 Self::quaternion(
763 self.i - other.i,
764 self.j - other.j,
765 self.k - other.k,
766 self.r - other.r,
767 )
768 }
769
770 #[inline]
771 fn mul(&self, factor: T) -> Self {
772 Self::quaternion(
773 self.i * factor,
774 self.j * factor,
775 self.k * factor,
776 self.r * factor,
777 )
778 }
779}
780
781impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation3D<T, Src, Dst> {
782 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
783 write!(
784 f,
785 "Quat({:?}*i + {:?}*j + {:?}*k + {:?})",
786 self.i, self.j, self.k, self.r
787 )
788 }
789}
790
791impl<T, Src, Dst> ApproxEq<T> for Rotation3D<T, Src, Dst>
792where
793 T: Copy + Neg<Output = T> + ApproxEq<T>,
794{
795 fn approx_epsilon() -> T {
796 T::approx_epsilon()
797 }
798
799 fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
800 (self.i.approx_eq_eps(&other.i, eps)
801 && self.j.approx_eq_eps(&other.j, eps)
802 && self.k.approx_eq_eps(&other.k, eps)
803 && self.r.approx_eq_eps(&other.r, eps))
804 || (self.i.approx_eq_eps(&-other.i, eps)
805 && self.j.approx_eq_eps(&-other.j, eps)
806 && self.k.approx_eq_eps(&-other.k, eps)
807 && self.r.approx_eq_eps(&-other.r, eps))
808 }
809}
810
811impl<T, Src, Dst> From<Rotation3D<T, Src, Dst>> for Transform3D<T, Src, Dst>
812where
813 T: Real + ApproxEq<T>,
814{
815 fn from(r: Rotation3D<T, Src, Dst>) -> Self {
816 r.to_transform()
817 }
818}
819
820#[test]
821fn simple_rotation_2d() {
822 use crate::default::Rotation2D;
823 use core::f32::consts::{FRAC_PI_2, PI};
824
825 let ri = Rotation2D::identity();
826 let r90 = Rotation2D::radians(FRAC_PI_2);
827 let rm90 = Rotation2D::radians(-FRAC_PI_2);
828 let r180 = Rotation2D::radians(PI);
829
830 assert!(ri
831 .transform_point(point2(1.0, 2.0))
832 .approx_eq(&point2(1.0, 2.0)));
833 assert!(r90
834 .transform_point(point2(1.0, 2.0))
835 .approx_eq(&point2(-2.0, 1.0)));
836 assert!(rm90
837 .transform_point(point2(1.0, 2.0))
838 .approx_eq(&point2(2.0, -1.0)));
839 assert!(r180
840 .transform_point(point2(1.0, 2.0))
841 .approx_eq(&point2(-1.0, -2.0)));
842
843 assert!(r90
844 .inverse()
845 .inverse()
846 .transform_point(point2(1.0, 2.0))
847 .approx_eq(&r90.transform_point(point2(1.0, 2.0))));
848}
849
850#[test]
851fn simple_rotation_3d_in_2d() {
852 use crate::default::Rotation3D;
853 use core::f32::consts::{FRAC_PI_2, PI};
854
855 let ri = Rotation3D::identity();
856 let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
857 let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2));
858 let r180 = Rotation3D::around_z(Angle::radians(PI));
859
860 assert!(ri
861 .transform_point2d(point2(1.0, 2.0))
862 .approx_eq(&point2(1.0, 2.0)));
863 assert!(r90
864 .transform_point2d(point2(1.0, 2.0))
865 .approx_eq(&point2(-2.0, 1.0)));
866 assert!(rm90
867 .transform_point2d(point2(1.0, 2.0))
868 .approx_eq(&point2(2.0, -1.0)));
869 assert!(r180
870 .transform_point2d(point2(1.0, 2.0))
871 .approx_eq(&point2(-1.0, -2.0)));
872
873 assert!(r90
874 .inverse()
875 .inverse()
876 .transform_point2d(point2(1.0, 2.0))
877 .approx_eq(&r90.transform_point2d(point2(1.0, 2.0))));
878}
879
880#[test]
881fn pre_post() {
882 use crate::default::Rotation3D;
883 use core::f32::consts::FRAC_PI_2;
884
885 let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2));
886 let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2));
887 let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
888
889 let t1 = r1.to_transform();
890 let t2 = r2.to_transform();
891 let t3 = r3.to_transform();
892
893 let p = point3(1.0, 2.0, 3.0);
894
895 let p1 = r1.then(&r2).then(&r3).transform_point3d(p);
898 let p2 = t1.then(&t2).then(&t3).transform_point3d(p);
899
900 assert!(p1.approx_eq(&p2.unwrap()));
901
902 let p3 = t3.then(&t1).then(&t2).transform_point3d(p);
904 assert!(!p1.approx_eq(&p3.unwrap()));
905}
906
907#[test]
908fn to_transform3d() {
909 use crate::default::Rotation3D;
910
911 use core::f32::consts::{FRAC_PI_2, PI};
912 let rotations = [
913 Rotation3D::identity(),
914 Rotation3D::around_x(Angle::radians(FRAC_PI_2)),
915 Rotation3D::around_x(Angle::radians(-FRAC_PI_2)),
916 Rotation3D::around_x(Angle::radians(PI)),
917 Rotation3D::around_y(Angle::radians(FRAC_PI_2)),
918 Rotation3D::around_y(Angle::radians(-FRAC_PI_2)),
919 Rotation3D::around_y(Angle::radians(PI)),
920 Rotation3D::around_z(Angle::radians(FRAC_PI_2)),
921 Rotation3D::around_z(Angle::radians(-FRAC_PI_2)),
922 Rotation3D::around_z(Angle::radians(PI)),
923 ];
924
925 let points = [
926 point3(0.0, 0.0, 0.0),
927 point3(1.0, 2.0, 3.0),
928 point3(-5.0, 3.0, -1.0),
929 point3(-0.5, -1.0, 1.5),
930 ];
931
932 for rotation in &rotations {
933 for &point in &points {
934 let p1 = rotation.transform_point3d(point);
935 let p2 = rotation.to_transform().transform_point3d(point);
936 assert!(p1.approx_eq(&p2.unwrap()));
937 }
938 }
939}
940
941#[test]
942fn slerp() {
943 use crate::default::Rotation3D;
944
945 let q1 = Rotation3D::quaternion(1.0, 0.0, 0.0, 0.0);
946 let q2 = Rotation3D::quaternion(0.0, 1.0, 0.0, 0.0);
947 let q3 = Rotation3D::quaternion(0.0, 0.0, -1.0, 0.0);
948
949 assert!(q1.slerp(&q2, 0.0).approx_eq(&q1));
957 assert!(q1.slerp(&q2, 0.2).approx_eq(&Rotation3D::quaternion(
958 0.951056516295154,
959 0.309016994374947,
960 0.0,
961 0.0
962 )));
963 assert!(q1.slerp(&q2, 0.4).approx_eq(&Rotation3D::quaternion(
964 0.809016994374947,
965 0.587785252292473,
966 0.0,
967 0.0
968 )));
969 assert!(q1.slerp(&q2, 0.6).approx_eq(&Rotation3D::quaternion(
970 0.587785252292473,
971 0.809016994374947,
972 0.0,
973 0.0
974 )));
975 assert!(q1.slerp(&q2, 0.8).approx_eq(&Rotation3D::quaternion(
976 0.309016994374947,
977 0.951056516295154,
978 0.0,
979 0.0
980 )));
981 assert!(q1.slerp(&q2, 1.0).approx_eq(&q2));
982
983 assert!(q1.slerp(&q3, 0.0).approx_eq(&q1));
984 assert!(q1.slerp(&q3, 0.2).approx_eq(&Rotation3D::quaternion(
985 0.951056516295154,
986 0.0,
987 -0.309016994374947,
988 0.0
989 )));
990 assert!(q1.slerp(&q3, 0.4).approx_eq(&Rotation3D::quaternion(
991 0.809016994374947,
992 0.0,
993 -0.587785252292473,
994 0.0
995 )));
996 assert!(q1.slerp(&q3, 0.6).approx_eq(&Rotation3D::quaternion(
997 0.587785252292473,
998 0.0,
999 -0.809016994374947,
1000 0.0
1001 )));
1002 assert!(q1.slerp(&q3, 0.8).approx_eq(&Rotation3D::quaternion(
1003 0.309016994374947,
1004 0.0,
1005 -0.951056516295154,
1006 0.0
1007 )));
1008 assert!(q1.slerp(&q3, 1.0).approx_eq(&q3));
1009}
1010
1011#[test]
1012fn around_axis() {
1013 use crate::default::Rotation3D;
1014 use core::f32::consts::{FRAC_PI_2, PI};
1015
1016 let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI));
1018 let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2));
1019 assert!(r1
1020 .transform_point3d(point3(1.0, 2.0, 0.0))
1021 .approx_eq(&point3(2.0, 1.0, 0.0)));
1022 assert!(r2
1023 .transform_point3d(point3(1.0, 0.0, 0.0))
1024 .approx_eq(&point3(0.5, 0.5, -0.5.sqrt())));
1025
1026 let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288));
1028 assert!(r3
1029 .transform_point3d(point3(1.0, 0.0, 0.0))
1030 .approx_eq(&point3(-0.58071821, 0.81401868, -0.01182979)));
1031}
1032
1033#[test]
1034fn from_euler() {
1035 use crate::default::Rotation3D;
1036 use core::f32::consts::FRAC_PI_2;
1037
1038 let p = point3(1.0, 2.0, 3.0);
1043
1044 let angle = Angle::radians(FRAC_PI_2);
1045 let zero = Angle::radians(0.0);
1046
1047 let roll_re = Rotation3D::euler(angle, zero, zero);
1049 let roll_rq = Rotation3D::around_x(angle);
1050 let roll_pe = roll_re.transform_point3d(p);
1051 let roll_pq = roll_rq.transform_point3d(p);
1052
1053 let pitch_re = Rotation3D::euler(zero, angle, zero);
1055 let pitch_rq = Rotation3D::around_y(angle);
1056 let pitch_pe = pitch_re.transform_point3d(p);
1057 let pitch_pq = pitch_rq.transform_point3d(p);
1058
1059 let yaw_re = Rotation3D::euler(zero, zero, angle);
1061 let yaw_rq = Rotation3D::around_z(angle);
1062 let yaw_pe = yaw_re.transform_point3d(p);
1063 let yaw_pq = yaw_rq.transform_point3d(p);
1064
1065 assert!(roll_pe.approx_eq(&roll_pq));
1066 assert!(pitch_pe.approx_eq(&pitch_pq));
1067 assert!(yaw_pe.approx_eq(&yaw_pq));
1068
1069 let ypr_e = Rotation3D::euler(angle, angle, angle);
1072 let ypr_q = roll_rq.then(&pitch_rq).then(&yaw_rq);
1073 let ypr_pe = ypr_e.transform_point3d(p);
1074 let ypr_pq = ypr_q.transform_point3d(p);
1075
1076 assert!(ypr_pe.approx_eq(&ypr_pq));
1077}