1use approxeq::ApproxEq;
11use num_traits::{Float, FloatConst, One, Zero, NumCast};
12use core::fmt;
13use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
14use core::marker::PhantomData;
15use trig::Trig;
16use {TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D, Vector3D, point2, point3, vec3};
17use {TypedTransform2D, TypedTransform3D, UnknownUnit};
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct Angle<T> {
23 pub radians: T,
24}
25
26impl<T> Angle<T> {
27 #[inline]
28 pub fn radians(radians: T) -> Self {
29 Angle { radians }
30 }
31
32 #[inline]
33 pub fn get(self) -> T {
34 self.radians
35 }
36}
37
38impl<T> Angle<T>
39where
40 T: Trig,
41{
42 #[inline]
43 pub fn degrees(deg: T) -> Self {
44 Angle {
45 radians: T::degrees_to_radians(deg),
46 }
47 }
48
49 #[inline]
50 pub fn to_degrees(self) -> T {
51 T::radians_to_degrees(self.radians)
52 }
53}
54
55impl<T> Angle<T>
56where
57 T: Rem<Output = T> + Sub<Output = T> + Add<Output = T> + Zero + FloatConst + PartialOrd + Copy,
58{
59 pub fn positive(&self) -> Self {
61 let two_pi = T::PI() + T::PI();
62 let mut a = self.radians % two_pi;
63 if a < T::zero() {
64 a = a + two_pi;
65 }
66 Angle::radians(a)
67 }
68
69 pub fn signed(&self) -> Self {
71 Angle::pi() - (Angle::pi() - *self).positive()
72 }
73}
74
75impl<T> Angle<T>
76where
77 T: Float,
78{
79 pub fn sin_cos(self) -> (T, T) {
81 self.radians.sin_cos()
82 }
83}
84
85impl<T> Angle<T>
86where
87 T: Zero,
88{
89 pub fn zero() -> Self {
90 Angle::radians(T::zero())
91 }
92}
93
94impl<T> Angle<T>
95where
96 T: FloatConst + Add<Output = T>,
97{
98 pub fn pi() -> Self {
99 Angle::radians(T::PI())
100 }
101
102 pub fn two_pi() -> Self {
103 Angle::radians(T::PI() + T::PI())
104 }
105
106 pub fn frac_pi_2() -> Self {
107 Angle::radians(T::FRAC_PI_2())
108 }
109
110 pub fn frac_pi_3() -> Self {
111 Angle::radians(T::FRAC_PI_3())
112 }
113
114 pub fn frac_pi_4() -> Self {
115 Angle::radians(T::FRAC_PI_4())
116 }
117}
118
119impl<T: Clone + Add<T, Output = T>> Add for Angle<T> {
120 type Output = Angle<T>;
121 fn add(self, other: Angle<T>) -> Angle<T> {
122 Angle::radians(self.radians + other.radians)
123 }
124}
125
126impl<T: Clone + AddAssign<T>> AddAssign for Angle<T> {
127 fn add_assign(&mut self, other: Angle<T>) {
128 self.radians += other.radians;
129 }
130}
131
132impl<T: Clone + Sub<T, Output = T>> Sub<Angle<T>> for Angle<T> {
133 type Output = Angle<T>;
134 fn sub(self, other: Angle<T>) -> <Self as Sub>::Output {
135 Angle::radians(self.radians - other.radians)
136 }
137}
138
139impl<T: Clone + SubAssign<T>> SubAssign for Angle<T> {
140 fn sub_assign(&mut self, other: Angle<T>) {
141 self.radians -= other.radians;
142 }
143}
144
145impl<T: Clone + Div<T, Output = T>> Div<Angle<T>> for Angle<T> {
146 type Output = T;
147 #[inline]
148 fn div(self, other: Angle<T>) -> T {
149 self.radians / other.radians
150 }
151}
152
153impl<T: Clone + Div<T, Output = T>> Div<T> for Angle<T> {
154 type Output = Angle<T>;
155 #[inline]
156 fn div(self, factor: T) -> Angle<T> {
157 Angle::radians(self.radians / factor)
158 }
159}
160
161impl<T: Clone + DivAssign<T>> DivAssign<T> for Angle<T> {
162 fn div_assign(&mut self, factor: T) {
163 self.radians /= factor;
164 }
165}
166
167impl<T: Clone + Mul<T, Output = T>> Mul<T> for Angle<T> {
168 type Output = Angle<T>;
169 #[inline]
170 fn mul(self, factor: T) -> Angle<T> {
171 Angle::radians(self.radians * factor)
172 }
173}
174
175impl<T: Clone + MulAssign<T>> MulAssign<T> for Angle<T> {
176 fn mul_assign(&mut self, factor: T) {
177 self.radians *= factor;
178 }
179}
180
181impl<T: Neg<Output = T>> Neg for Angle<T> {
182 type Output = Self;
183 fn neg(self) -> Self {
184 Angle::radians(-self.radians)
185 }
186}
187
188#[derive(EuclidMatrix)]
190#[repr(C)]
191pub struct TypedRotation2D<T, Src, Dst> {
192 pub angle : T,
193 #[doc(hidden)]
194 pub _unit: PhantomData<(Src, Dst)>,
195}
196
197pub type Rotation2D<T> = TypedRotation2D<T, UnknownUnit, UnknownUnit>;
199
200impl<T, Src, Dst> TypedRotation2D<T, Src, Dst> {
201 #[inline]
202 pub fn new(angle: Angle<T>) -> Self {
204 TypedRotation2D {
205 angle: angle.radians,
206 _unit: PhantomData,
207 }
208 }
209
210 pub fn radians(angle: T) -> Self {
211 Self::new(Angle::radians(angle))
212 }
213
214 #[inline]
216 pub fn identity() -> Self
217 where
218 T: Zero,
219 {
220 Self::radians(T::zero())
221 }
222}
223
224impl<T, Src, Dst> TypedRotation2D<T, Src, Dst>
225where
226 T: Clone,
227{
228 pub fn get_angle(&self) -> Angle<T> {
230 Angle::radians(self.angle.clone())
231 }
232}
233
234impl<T, Src, Dst> TypedRotation2D<T, Src, Dst>
235where
236 T: Copy
237 + Clone
238 + Add<T, Output = T>
239 + Sub<T, Output = T>
240 + Mul<T, Output = T>
241 + Div<T, Output = T>
242 + Neg<Output = T>
243 + PartialOrd
244 + Float
245 + One
246 + Zero,
247{
248 #[inline]
250 pub fn to_3d(&self) -> TypedRotation3D<T, Src, Dst> {
251 TypedRotation3D::around_z(self.get_angle())
252 }
253
254 #[inline]
256 pub fn inverse(&self) -> TypedRotation2D<T, Dst, Src> {
257 TypedRotation2D::radians(-self.angle)
258 }
259
260 #[inline]
262 pub fn pre_rotate<NewSrc>(
263 &self,
264 other: &TypedRotation2D<T, NewSrc, Src>,
265 ) -> TypedRotation2D<T, NewSrc, Dst> {
266 TypedRotation2D::radians(self.angle + other.angle)
267 }
268
269 #[inline]
271 pub fn post_rotate<NewDst>(
272 &self,
273 other: &TypedRotation2D<T, Dst, NewDst>,
274 ) -> TypedRotation2D<T, Src, NewDst> {
275 other.pre_rotate(self)
276 }
277
278 #[inline]
282 pub fn transform_point(&self, point: &TypedPoint2D<T, Src>) -> TypedPoint2D<T, Dst> {
283 let (sin, cos) = Float::sin_cos(self.angle);
284 point2(point.x * cos - point.y * sin, point.y * cos + point.x * sin)
285 }
286
287 #[inline]
291 pub fn transform_vector(&self, vector: &TypedVector2D<T, Src>) -> TypedVector2D<T, Dst> {
292 self.transform_point(&vector.to_point()).to_vector()
293 }
294}
295
296impl<T, Src, Dst> TypedRotation2D<T, Src, Dst>
297where
298 T: Copy
299 + Clone
300 + Add<T, Output = T>
301 + Mul<T, Output = T>
302 + Div<T, Output = T>
303 + Sub<T, Output = T>
304 + Trig
305 + PartialOrd
306 + One
307 + Zero,
308{
309 #[inline]
311 pub fn to_transform(&self) -> TypedTransform2D<T, Src, Dst> {
312 TypedTransform2D::create_rotation(self.get_angle())
313 }
314}
315
316#[derive(EuclidMatrix)]
326#[repr(C)]
327pub struct TypedRotation3D<T, Src, Dst> {
328 pub i: T,
330 pub j: T,
332 pub k: T,
334 pub r: T,
336 #[doc(hidden)]
337 pub _unit: PhantomData<(Src, Dst)>,
338}
339
340pub type Rotation3D<T> = TypedRotation3D<T, UnknownUnit, UnknownUnit>;
342
343impl<T, Src, Dst> TypedRotation3D<T, Src, Dst> {
344 #[inline]
352 pub fn quaternion(a: T, b: T, c: T, r: T) -> Self {
353 TypedRotation3D {
354 i: a,
355 j: b,
356 k: c,
357 r,
358 _unit: PhantomData,
359 }
360 }
361}
362
363impl<T, Src, Dst> TypedRotation3D<T, Src, Dst>
364where
365 T: Copy,
366{
367 #[inline]
369 pub fn vector_part(&self) -> Vector3D<T> {
370 vec3(self.i, self.j, self.k)
371 }
372}
373
374impl<T, Src, Dst> TypedRotation3D<T, Src, Dst>
375where
376 T: Float,
377{
378 #[inline]
380 pub fn identity() -> Self {
381 let zero = T::zero();
382 let one = T::one();
383 Self::quaternion(zero, zero, zero, one)
384 }
385
386 #[inline]
392 pub fn unit_quaternion(i: T, j: T, k: T, r: T) -> Self {
393 Self::quaternion(i, j, k, r).normalize()
394 }
395
396 pub fn around_axis(axis: TypedVector3D<T, Src>, angle: Angle<T>) -> Self {
398 let axis = axis.normalize();
399 let two = T::one() + T::one();
400 let (sin, cos) = Angle::sin_cos(angle / two);
401 Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos)
402 }
403
404 pub fn around_x(angle: Angle<T>) -> Self {
406 let zero = Zero::zero();
407 let two = T::one() + T::one();
408 let (sin, cos) = Angle::sin_cos(angle / two);
409 Self::quaternion(sin, zero, zero, cos)
410 }
411
412 pub fn around_y(angle: Angle<T>) -> Self {
414 let zero = Zero::zero();
415 let two = T::one() + T::one();
416 let (sin, cos) = Angle::sin_cos(angle / two);
417 Self::quaternion(zero, sin, zero, cos)
418 }
419
420 pub fn around_z(angle: Angle<T>) -> Self {
422 let zero = Zero::zero();
423 let two = T::one() + T::one();
424 let (sin, cos) = Angle::sin_cos(angle / two);
425 Self::quaternion(zero, zero, sin, cos)
426 }
427
428 pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self {
436 let half = T::one() / (T::one() + T::one());
437
438 let (sy, cy) = Float::sin_cos(half * yaw.get());
439 let (sp, cp) = Float::sin_cos(half * pitch.get());
440 let (sr, cr) = Float::sin_cos(half * roll.get());
441
442 Self::quaternion(
443 cy * sr * cp - sy * cr * sp,
444 cy * cr * sp + sy * sr * cp,
445 sy * cr * cp - cy * sr * sp,
446 cy * cr * cp + sy * sr * sp,
447 )
448 }
449
450 #[inline]
452 pub fn inverse(&self) -> TypedRotation3D<T, Dst, Src> {
453 TypedRotation3D::quaternion(-self.i, -self.j, -self.k, self.r)
454 }
455
456 #[inline]
458 pub fn norm(&self) -> T {
459 self.square_norm().sqrt()
460 }
461
462 #[inline]
463 pub fn square_norm(&self) -> T {
464 (self.i * self.i + self.j * self.j + self.k * self.k + self.r * self.r)
465 }
466
467 #[inline]
469 pub fn normalize(&self) -> Self {
470 self.mul(T::one() / self.norm())
471 }
472
473 #[inline]
474 pub fn is_normalized(&self) -> bool
475 where
476 T: ApproxEq<T>,
477 {
478 let eps = NumCast::from(1.0e-5).unwrap();
479 self.square_norm().approx_eq_eps(&T::one(), &eps)
480 }
481
482 pub fn slerp(&self, other: &Self, t: T) -> Self
486 where
487 T: ApproxEq<T>,
488 {
489 debug_assert!(self.is_normalized());
490 debug_assert!(other.is_normalized());
491
492 let r1 = *self;
493 let mut r2 = *other;
494
495 let mut dot = r1.i * r2.i + r1.j * r2.j + r1.k * r2.k + r1.r * r2.r;
496
497 let one = T::one();
498
499 if dot.approx_eq(&T::one()) {
500 return r1.lerp(&r2, t);
502 }
503
504 if dot < T::zero() {
508 r2 = r2.mul(-T::one());
509 dot = -dot;
510 }
511
512 dot = Float::min(dot, one);
514
515 let theta = Float::acos(dot) * t;
517
518 let r3 = r2.sub(r1.mul(dot)).normalize();
520 let (sin, cos) = Float::sin_cos(theta);
521 r1.mul(cos).add(r3.mul(sin))
522 }
523
524 #[inline]
528 pub fn lerp(&self, other: &Self, t: T) -> Self {
529 let one_t = T::one() - t;
530 self.mul(one_t).add(other.mul(t)).normalize()
531 }
532
533 pub fn rotate_point3d(&self, point: &TypedPoint3D<T, Src>) -> TypedPoint3D<T, Dst>
537 where
538 T: ApproxEq<T>,
539 {
540 debug_assert!(self.is_normalized());
541
542 let two = T::one() + T::one();
543 let cross = self.vector_part().cross(point.to_vector().to_untyped()) * two;
544
545 point3(
546 point.x + self.r * cross.x + self.j * cross.z - self.k * cross.y,
547 point.y + self.r * cross.y + self.k * cross.x - self.i * cross.z,
548 point.z + self.r * cross.z + self.i * cross.y - self.j * cross.x,
549 )
550 }
551
552 #[inline]
556 pub fn rotate_point2d(&self, point: &TypedPoint2D<T, Src>) -> TypedPoint2D<T, Dst>
557 where
558 T: ApproxEq<T>,
559 {
560 self.rotate_point3d(&point.to_3d()).xy()
561 }
562
563 #[inline]
567 pub fn rotate_vector3d(&self, vector: &TypedVector3D<T, Src>) -> TypedVector3D<T, Dst>
568 where
569 T: ApproxEq<T>,
570 {
571 self.rotate_point3d(&vector.to_point()).to_vector()
572 }
573
574 #[inline]
578 pub fn rotate_vector2d(&self, vector: &TypedVector2D<T, Src>) -> TypedVector2D<T, Dst>
579 where
580 T: ApproxEq<T>,
581 {
582 self.rotate_vector3d(&vector.to_3d()).xy()
583 }
584
585 #[inline]
587 pub fn to_transform(&self) -> TypedTransform3D<T, Src, Dst>
588 where
589 T: ApproxEq<T>,
590 {
591 debug_assert!(self.is_normalized());
592
593 let i2 = self.i + self.i;
594 let j2 = self.j + self.j;
595 let k2 = self.k + self.k;
596 let ii = self.i * i2;
597 let ij = self.i * j2;
598 let ik = self.i * k2;
599 let jj = self.j * j2;
600 let jk = self.j * k2;
601 let kk = self.k * k2;
602 let ri = self.r * i2;
603 let rj = self.r * j2;
604 let rk = self.r * k2;
605
606 let one = T::one();
607 let zero = T::zero();
608
609 let m11 = one - (jj + kk);
610 let m12 = ij + rk;
611 let m13 = ik - rj;
612
613 let m21 = ij - rk;
614 let m22 = one - (ii + kk);
615 let m23 = jk + ri;
616
617 let m31 = ik + rj;
618 let m32 = jk - ri;
619 let m33 = one - (ii + jj);
620
621 TypedTransform3D::row_major(
622 m11,
623 m12,
624 m13,
625 zero,
626 m21,
627 m22,
628 m23,
629 zero,
630 m31,
631 m32,
632 m33,
633 zero,
634 zero,
635 zero,
636 zero,
637 one,
638 )
639 }
640
641 pub fn pre_rotate<NewSrc>(
643 &self,
644 other: &TypedRotation3D<T, NewSrc, Src>,
645 ) -> TypedRotation3D<T, NewSrc, Dst>
646 where
647 T: ApproxEq<T>,
648 {
649 debug_assert!(self.is_normalized());
650 TypedRotation3D::quaternion(
651 self.i * other.r + self.r * other.i + self.j * other.k - self.k * other.j,
652 self.j * other.r + self.r * other.j + self.k * other.i - self.i * other.k,
653 self.k * other.r + self.r * other.k + self.i * other.j - self.j * other.i,
654 self.r * other.r - self.i * other.i - self.j * other.j - self.k * other.k,
655 )
656 }
657
658 #[inline]
660 pub fn post_rotate<NewDst>(
661 &self,
662 other: &TypedRotation3D<T, Dst, NewDst>,
663 ) -> TypedRotation3D<T, Src, NewDst>
664 where
665 T: ApproxEq<T>,
666 {
667 other.pre_rotate(self)
668 }
669
670 #[inline]
674 fn add(&self, other: Self) -> Self {
675 Self::quaternion(
676 self.i + other.i,
677 self.j + other.j,
678 self.k + other.k,
679 self.r + other.r,
680 )
681 }
682
683 #[inline]
684 fn sub(&self, other: Self) -> Self {
685 Self::quaternion(
686 self.i - other.i,
687 self.j - other.j,
688 self.k - other.k,
689 self.r - other.r,
690 )
691 }
692
693 #[inline]
694 fn mul(&self, factor: T) -> Self {
695 Self::quaternion(
696 self.i * factor,
697 self.j * factor,
698 self.k * factor,
699 self.r * factor,
700 )
701 }
702}
703
704impl<T: fmt::Debug, Src, Dst> fmt::Debug for TypedRotation3D<T, Src, Dst> {
705 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
706 write!(
707 f,
708 "Quat({:?}*i + {:?}*j + {:?}*k + {:?})",
709 self.i, self.j, self.k, self.r
710 )
711 }
712}
713
714impl<T: fmt::Display, Src, Dst> fmt::Display for TypedRotation3D<T, Src, Dst> {
715 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
716 write!(
717 f,
718 "Quat({}*i + {}*j + {}*k + {})",
719 self.i, self.j, self.k, self.r
720 )
721 }
722}
723
724impl<T, Src, Dst> ApproxEq<T> for TypedRotation3D<T, Src, Dst>
725where
726 T: Copy + Neg<Output = T> + ApproxEq<T>,
727{
728 fn approx_epsilon() -> T {
729 T::approx_epsilon()
730 }
731
732 fn approx_eq(&self, other: &Self) -> bool {
733 self.approx_eq_eps(other, &Self::approx_epsilon())
734 }
735
736 fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
737 (self.i.approx_eq_eps(&other.i, eps) && self.j.approx_eq_eps(&other.j, eps)
738 && self.k.approx_eq_eps(&other.k, eps) && self.r.approx_eq_eps(&other.r, eps))
739 || (self.i.approx_eq_eps(&-other.i, eps) && self.j.approx_eq_eps(&-other.j, eps)
740 && self.k.approx_eq_eps(&-other.k, eps)
741 && self.r.approx_eq_eps(&-other.r, eps))
742 }
743}
744
745#[test]
746fn simple_rotation_2d() {
747 use core::f32::consts::{FRAC_PI_2, PI};
748 let ri = Rotation2D::identity();
749 let r90 = Rotation2D::radians(FRAC_PI_2);
750 let rm90 = Rotation2D::radians(-FRAC_PI_2);
751 let r180 = Rotation2D::radians(PI);
752
753 assert!(
754 ri.transform_point(&point2(1.0, 2.0))
755 .approx_eq(&point2(1.0, 2.0))
756 );
757 assert!(
758 r90.transform_point(&point2(1.0, 2.0))
759 .approx_eq(&point2(-2.0, 1.0))
760 );
761 assert!(
762 rm90.transform_point(&point2(1.0, 2.0))
763 .approx_eq(&point2(2.0, -1.0))
764 );
765 assert!(
766 r180.transform_point(&point2(1.0, 2.0))
767 .approx_eq(&point2(-1.0, -2.0))
768 );
769
770 assert!(
771 r90.inverse()
772 .inverse()
773 .transform_point(&point2(1.0, 2.0))
774 .approx_eq(&r90.transform_point(&point2(1.0, 2.0)))
775 );
776}
777
778#[test]
779fn simple_rotation_3d_in_2d() {
780 use core::f32::consts::{FRAC_PI_2, PI};
781 let ri = Rotation3D::identity();
782 let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
783 let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2));
784 let r180 = Rotation3D::around_z(Angle::radians(PI));
785
786 assert!(
787 ri.rotate_point2d(&point2(1.0, 2.0))
788 .approx_eq(&point2(1.0, 2.0))
789 );
790 assert!(
791 r90.rotate_point2d(&point2(1.0, 2.0))
792 .approx_eq(&point2(-2.0, 1.0))
793 );
794 assert!(
795 rm90.rotate_point2d(&point2(1.0, 2.0))
796 .approx_eq(&point2(2.0, -1.0))
797 );
798 assert!(
799 r180.rotate_point2d(&point2(1.0, 2.0))
800 .approx_eq(&point2(-1.0, -2.0))
801 );
802
803 assert!(
804 r90.inverse()
805 .inverse()
806 .rotate_point2d(&point2(1.0, 2.0))
807 .approx_eq(&r90.rotate_point2d(&point2(1.0, 2.0)))
808 );
809}
810
811#[test]
812fn pre_post() {
813 use core::f32::consts::FRAC_PI_2;
814 let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2));
815 let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2));
816 let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
817
818 let t1 = r1.to_transform();
819 let t2 = r2.to_transform();
820 let t3 = r3.to_transform();
821
822 let p = point3(1.0, 2.0, 3.0);
823
824 let p1 = r1.post_rotate(&r2).post_rotate(&r3).rotate_point3d(&p);
827 let p2 = t1.post_mul(&t2).post_mul(&t3).transform_point3d(&p);
828
829 assert!(p1.approx_eq(&p2.unwrap()));
830
831 let p3 = t3.post_mul(&t1).post_mul(&t2).transform_point3d(&p);
833 assert!(!p1.approx_eq(&p3.unwrap()));
834}
835
836#[test]
837fn to_transform3d() {
838 use core::f32::consts::{FRAC_PI_2, PI};
839 let rotations = [
840 Rotation3D::identity(),
841 Rotation3D::around_x(Angle::radians(FRAC_PI_2)),
842 Rotation3D::around_x(Angle::radians(-FRAC_PI_2)),
843 Rotation3D::around_x(Angle::radians(PI)),
844 Rotation3D::around_y(Angle::radians(FRAC_PI_2)),
845 Rotation3D::around_y(Angle::radians(-FRAC_PI_2)),
846 Rotation3D::around_y(Angle::radians(PI)),
847 Rotation3D::around_z(Angle::radians(FRAC_PI_2)),
848 Rotation3D::around_z(Angle::radians(-FRAC_PI_2)),
849 Rotation3D::around_z(Angle::radians(PI)),
850 ];
851
852 let points = [
853 point3(0.0, 0.0, 0.0),
854 point3(1.0, 2.0, 3.0),
855 point3(-5.0, 3.0, -1.0),
856 point3(-0.5, -1.0, 1.5),
857 ];
858
859 for rotation in &rotations {
860 for point in &points {
861 let p1 = rotation.rotate_point3d(point);
862 let p2 = rotation.to_transform().transform_point3d(point);
863 assert!(p1.approx_eq(&p2.unwrap()));
864 }
865 }
866}
867
868#[test]
869fn slerp() {
870 let q1 = Rotation3D::quaternion(1.0, 0.0, 0.0, 0.0);
871 let q2 = Rotation3D::quaternion(0.0, 1.0, 0.0, 0.0);
872 let q3 = Rotation3D::quaternion(0.0, 0.0, -1.0, 0.0);
873
874 assert!(q1.slerp(&q2, 0.0).approx_eq(&q1));
882 assert!(q1.slerp(&q2, 0.2).approx_eq(&Rotation3D::quaternion(
883 0.951056516295154,
884 0.309016994374947,
885 0.0,
886 0.0
887 )));
888 assert!(q1.slerp(&q2, 0.4).approx_eq(&Rotation3D::quaternion(
889 0.809016994374947,
890 0.587785252292473,
891 0.0,
892 0.0
893 )));
894 assert!(q1.slerp(&q2, 0.6).approx_eq(&Rotation3D::quaternion(
895 0.587785252292473,
896 0.809016994374947,
897 0.0,
898 0.0
899 )));
900 assert!(q1.slerp(&q2, 0.8).approx_eq(&Rotation3D::quaternion(
901 0.309016994374947,
902 0.951056516295154,
903 0.0,
904 0.0
905 )));
906 assert!(q1.slerp(&q2, 1.0).approx_eq(&q2));
907
908 assert!(q1.slerp(&q3, 0.0).approx_eq(&q1));
909 assert!(q1.slerp(&q3, 0.2).approx_eq(&Rotation3D::quaternion(
910 0.951056516295154,
911 0.0,
912 -0.309016994374947,
913 0.0
914 )));
915 assert!(q1.slerp(&q3, 0.4).approx_eq(&Rotation3D::quaternion(
916 0.809016994374947,
917 0.0,
918 -0.587785252292473,
919 0.0
920 )));
921 assert!(q1.slerp(&q3, 0.6).approx_eq(&Rotation3D::quaternion(
922 0.587785252292473,
923 0.0,
924 -0.809016994374947,
925 0.0
926 )));
927 assert!(q1.slerp(&q3, 0.8).approx_eq(&Rotation3D::quaternion(
928 0.309016994374947,
929 0.0,
930 -0.951056516295154,
931 0.0
932 )));
933 assert!(q1.slerp(&q3, 1.0).approx_eq(&q3));
934}
935
936#[test]
937fn around_axis() {
938 use core::f32::consts::{FRAC_PI_2, PI};
939
940 let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI));
942 let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2));
943 assert!(
944 r1.rotate_point3d(&point3(1.0, 2.0, 0.0))
945 .approx_eq(&point3(2.0, 1.0, 0.0))
946 );
947 assert!(
948 r2.rotate_point3d(&point3(1.0, 0.0, 0.0))
949 .approx_eq(&point3(0.5, 0.5, -0.5.sqrt()))
950 );
951
952 let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288));
954 assert!(r3.rotate_point3d(&point3(1.0, 0.0, 0.0)).approx_eq(&point3(
955 -0.58071821,
956 0.81401868,
957 -0.01182979
958 )));
959}
960
961#[test]
962fn from_euler() {
963 use core::f32::consts::FRAC_PI_2;
964
965 let p = point3(1.0, 2.0, 3.0);
970
971 let angle = Angle::radians(FRAC_PI_2);
972 let zero = Angle::radians(0.0);
973
974 let roll_re = Rotation3D::euler(angle, zero, zero);
976 let roll_rq = Rotation3D::around_x(angle);
977 let roll_pe = roll_re.rotate_point3d(&p);
978 let roll_pq = roll_rq.rotate_point3d(&p);
979
980 let pitch_re = Rotation3D::euler(zero, angle, zero);
982 let pitch_rq = Rotation3D::around_y(angle);
983 let pitch_pe = pitch_re.rotate_point3d(&p);
984 let pitch_pq = pitch_rq.rotate_point3d(&p);
985
986 let yaw_re = Rotation3D::euler(zero, zero, angle);
988 let yaw_rq = Rotation3D::around_z(angle);
989 let yaw_pe = yaw_re.rotate_point3d(&p);
990 let yaw_pq = yaw_rq.rotate_point3d(&p);
991
992 assert!(roll_pe.approx_eq(&roll_pq));
993 assert!(pitch_pe.approx_eq(&pitch_pq));
994 assert!(yaw_pe.approx_eq(&yaw_pq));
995
996 let ypr_e = Rotation3D::euler(angle, angle, angle);
999 let ypr_q = roll_rq.post_rotate(&pitch_rq).post_rotate(&yaw_rq);
1000 let ypr_pe = ypr_e.rotate_point3d(&p);
1001 let ypr_pq = ypr_q.rotate_point3d(&p);
1002
1003 assert!(ypr_pe.approx_eq(&ypr_pq));
1004}
1005
1006#[test]
1007fn wrap_angles() {
1008 use core::f32::consts::{FRAC_PI_2, PI};
1009 assert!(Angle::radians(0.0).positive().radians.approx_eq(&0.0));
1010 assert!(
1011 Angle::radians(FRAC_PI_2)
1012 .positive()
1013 .radians
1014 .approx_eq(&FRAC_PI_2)
1015 );
1016 assert!(
1017 Angle::radians(-FRAC_PI_2)
1018 .positive()
1019 .radians
1020 .approx_eq(&(3.0 * FRAC_PI_2))
1021 );
1022 assert!(
1023 Angle::radians(3.0 * FRAC_PI_2)
1024 .positive()
1025 .radians
1026 .approx_eq(&(3.0 * FRAC_PI_2))
1027 );
1028 assert!(
1029 Angle::radians(5.0 * FRAC_PI_2)
1030 .positive()
1031 .radians
1032 .approx_eq(&FRAC_PI_2)
1033 );
1034 assert!(Angle::radians(2.0 * PI).positive().radians.approx_eq(&0.0));
1035 assert!(Angle::radians(-2.0 * PI).positive().radians.approx_eq(&0.0));
1036 assert!(Angle::radians(PI).positive().radians.approx_eq(&PI));
1037 assert!(Angle::radians(-PI).positive().radians.approx_eq(&PI));
1038
1039 assert!(
1040 Angle::radians(FRAC_PI_2)
1041 .signed()
1042 .radians
1043 .approx_eq(&FRAC_PI_2)
1044 );
1045 assert!(
1046 Angle::radians(3.0 * FRAC_PI_2)
1047 .signed()
1048 .radians
1049 .approx_eq(&-FRAC_PI_2)
1050 );
1051 assert!(
1052 Angle::radians(5.0 * FRAC_PI_2)
1053 .signed()
1054 .radians
1055 .approx_eq(&FRAC_PI_2)
1056 );
1057 assert!(Angle::radians(2.0 * PI).signed().radians.approx_eq(&0.0));
1058 assert!(Angle::radians(-2.0 * PI).signed().radians.approx_eq(&0.0));
1059 assert!(Angle::radians(-PI).signed().radians.approx_eq(&PI));
1060 assert!(Angle::radians(PI).signed().radians.approx_eq(&PI));
1061}