euclid/
rotation.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use 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/// An angle in radians
20#[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    /// Returns this angle in the [0..2*PI[ range.
60    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    /// Returns this angle in the ]-PI..PI] range.
70    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    /// Returns (sin(self), cos(self)).
80    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/// A transform that can represent rotations in 2d, represented as an angle in radians.
189#[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
197/// The default 2d rotation type with no units.
198pub type Rotation2D<T> = TypedRotation2D<T, UnknownUnit, UnknownUnit>;
199
200impl<T, Src, Dst> TypedRotation2D<T, Src, Dst> {
201    #[inline]
202    /// Creates a rotation from an angle in radians.
203    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    /// Creates the identity rotation.
215    #[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    /// Returns self.angle as a strongly typed `Angle<T>`.
229    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    /// Creates a 3d rotation (around the z axis) from this 2d rotation.
249    #[inline]
250    pub fn to_3d(&self) -> TypedRotation3D<T, Src, Dst> {
251        TypedRotation3D::around_z(self.get_angle())
252    }
253
254    /// Returns the inverse of this rotation.
255    #[inline]
256    pub fn inverse(&self) -> TypedRotation2D<T, Dst, Src> {
257        TypedRotation2D::radians(-self.angle)
258    }
259
260    /// Returns a rotation representing the other rotation followed by this rotation.
261    #[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    /// Returns a rotation representing this rotation followed by the other rotation.
270    #[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    /// Returns the given 2d point transformed by this rotation.
279    ///
280    /// The input point must be use the unit Src, and the returned point has the unit Dst.
281    #[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    /// Returns the given 2d vector transformed by this rotation.
288    ///
289    /// The input point must be use the unit Src, and the returned point has the unit Dst.
290    #[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    /// Returns the matrix representation of this rotation.
310    #[inline]
311    pub fn to_transform(&self) -> TypedTransform2D<T, Src, Dst> {
312        TypedTransform2D::create_rotation(self.get_angle())
313    }
314}
315
316/// A transform that can represent rotations in 3d, represented as a quaternion.
317///
318/// Most methods expect the quaternion to be normalized.
319/// When in doubt, use `unit_quaternion` instead of `quaternion` to create
320/// a rotation as the former will ensure that its result is normalized.
321///
322/// Some people use the `x, y, z, w` (or `w, x, y, z`) notations. The equivalence is
323/// as follows: `x -> i`, `y -> j`, `z -> k`, `w -> r`.
324/// The memory layout of this type corresponds to the `x, y, z, w` notation
325#[derive(EuclidMatrix)]
326#[repr(C)]
327pub struct TypedRotation3D<T, Src, Dst> {
328    /// Component multiplied by the imaginary number `i`.
329    pub i: T,
330    /// Component multiplied by the imaginary number `j`.
331    pub j: T,
332    /// Component multiplied by the imaginary number `k`.
333    pub k: T,
334    /// The real part.
335    pub r: T,
336    #[doc(hidden)]
337    pub _unit: PhantomData<(Src, Dst)>,
338}
339
340/// The default 3d rotation type with no units.
341pub type Rotation3D<T> = TypedRotation3D<T, UnknownUnit, UnknownUnit>;
342
343impl<T, Src, Dst> TypedRotation3D<T, Src, Dst> {
344    /// Creates a rotation around from a quaternion representation.
345    ///
346    /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r`
347    /// where `a`, `b` and `c` describe the vector part and the last parameter `r` is
348    /// the real part.
349    ///
350    /// The resulting quaternion is not necessarily normalized. See `unit_quaternion`.
351    #[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    /// Returns the vector part (i, j, k) of this quaternion.
368    #[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    /// Creates the identity rotation.
379    #[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    /// Creates a rotation around from a quaternion representation and normalizes it.
387    ///
388    /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r`
389    /// before normalization, where `a`, `b` and `c` describe the vector part and the
390    /// last parameter `r` is the real part.
391    #[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    /// Creates a rotation around a given axis.
397    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    /// Creates a rotation around the x axis.
405    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    /// Creates a rotation around the y axis.
413    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    /// Creates a rotation around the z axis.
421    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    /// Creates a rotation from Euler angles.
429    ///
430    /// The rotations are applied in roll then pitch then yaw order.
431    ///
432    ///  - Roll (also called bank) is a rotation around the x axis.
433    ///  - Pitch (also called bearing) is a rotation around the y axis.
434    ///  - Yaw (also called heading) is a rotation around the z axis.
435    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    /// Returns the inverse of this rotation.
451    #[inline]
452    pub fn inverse(&self) -> TypedRotation3D<T, Dst, Src> {
453        TypedRotation3D::quaternion(-self.i, -self.j, -self.k, self.r)
454    }
455
456    /// Computes the norm of this quaternion
457    #[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    /// Returns a unit quaternion from this one.
468    #[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    /// Spherical linear interpolation between this rotation and another rotation.
483    ///
484    /// `t` is expected to be between zero and one.
485    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            // If the inputs are too close, linearly interpolate to avoid precision issues.
501            return r1.lerp(&r2, t);
502        }
503
504        // If the dot product is negative, the quaternions
505        // have opposite handed-ness and slerp won't take
506        // the shorter path. Fix by reversing one quaternion.
507        if dot < T::zero() {
508            r2 = r2.mul(-T::one());
509            dot = -dot;
510        }
511
512        // For robustness, stay within the domain of acos.
513        dot = Float::min(dot, one);
514
515        // Angle between r1 and the result.
516        let theta = Float::acos(dot) * t;
517
518        // r1 and r3 form an orthonormal basis.
519        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    /// Basic Linear interpolation between this rotation and another rotation.
525    ///
526    /// `t` is expected to be between zero and one.
527    #[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    /// Returns the given 3d point transformed by this rotation.
534    ///
535    /// The input point must be use the unit Src, and the returned point has the unit Dst.
536    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    /// Returns the given 2d point transformed by this rotation then projected on the xy plane.
553    ///
554    /// The input point must be use the unit Src, and the returned point has the unit Dst.
555    #[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    /// Returns the given 3d vector transformed by this rotation.
564    ///
565    /// The input vector must be use the unit Src, and the returned point has the unit Dst.
566    #[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    /// Returns the given 2d vector transformed by this rotation then projected on the xy plane.
575    ///
576    /// The input vector must be use the unit Src, and the returned point has the unit Dst.
577    #[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    /// Returns the matrix representation of this rotation.
586    #[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    /// Returns a rotation representing the other rotation followed by this rotation.
642    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    /// Returns a rotation representing this rotation followed by the other rotation.
659    #[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    // add, sub and mul are used internally for intermediate computation but aren't public
671    // because they don't carry real semantic meanings (I think?).
672
673    #[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    // Check that the order of transformations is correct (corresponds to what
825    // we do in Transform3D).
826    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    // Check that changing the order indeed matters.
832    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    // The values below can be obtained with a python program:
875    // import numpy
876    // import quaternion
877    // q1 = numpy.quaternion(1, 0, 0, 0)
878    // q2 = numpy.quaternion(0, 1, 0, 0)
879    // quaternion.slerp_evaluate(q1, q2, 0.2)
880
881    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    // Two sort of trivial cases:
941    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    // A more arbitrary test (made up with numpy):
953    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    // First test simple separate yaw pitch and roll rotations, because it is easy to come
966    // up with the corresponding quaternion.
967    // Since several quaternions can represent the same transformation we compare the result
968    // of transforming a point rather than the values of each quaternions.
969    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    // roll
975    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    // pitch
981    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    // yaw
987    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    // Now check that the yaw pitch and roll transformations when combined are applied in
997    // the proper order: roll -> pitch -> yaw.
998    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}