bevy_math/
rotation2d.rs

1use core::f32::consts::TAU;
2
3use glam::FloatExt;
4
5use crate::{
6    ops,
7    prelude::{Mat2, Vec2},
8};
9
10#[cfg(feature = "bevy_reflect")]
11use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
13use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
14
15/// A 2D rotation.
16///
17/// # Example
18///
19/// ```
20/// # use approx::assert_relative_eq;
21/// # use bevy_math::{Rot2, Vec2};
22/// use std::f32::consts::PI;
23///
24/// // Create rotations from counterclockwise angles in radians or degrees
25/// let rotation1 = Rot2::radians(PI / 2.0);
26/// let rotation2 = Rot2::degrees(45.0);
27///
28/// // Get the angle back as radians or degrees
29/// assert_eq!(rotation1.as_degrees(), 90.0);
30/// assert_eq!(rotation2.as_radians(), PI / 4.0);
31///
32/// // "Add" rotations together using `*`
33/// #[cfg(feature = "approx")]
34/// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0));
35///
36/// // Rotate vectors
37/// #[cfg(feature = "approx")]
38/// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y);
39/// ```
40#[derive(Clone, Copy, Debug, PartialEq)]
41#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
42#[cfg_attr(
43    feature = "bevy_reflect",
44    derive(Reflect),
45    reflect(Debug, PartialEq, Default, Clone)
46)]
47#[cfg_attr(
48    all(feature = "serialize", feature = "bevy_reflect"),
49    reflect(Serialize, Deserialize)
50)]
51#[doc(alias = "rotation", alias = "rotation2d", alias = "rotation_2d")]
52pub struct Rot2 {
53    /// The cosine of the rotation angle.
54    ///
55    /// This is the real part of the unit complex number representing the rotation.
56    pub cos: f32,
57    /// The sine of the rotation angle.
58    ///
59    /// This is the imaginary part of the unit complex number representing the rotation.
60    pub sin: f32,
61}
62
63impl Default for Rot2 {
64    fn default() -> Self {
65        Self::IDENTITY
66    }
67}
68
69impl Rot2 {
70    /// No rotation.
71    /// Also equals a full turn that returns back to its original position.
72    ///  ```
73    /// # use approx::assert_relative_eq;
74    /// # use bevy_math::Rot2;
75    /// #[cfg(feature = "approx")]
76    /// assert_relative_eq!(Rot2::IDENTITY, Rot2::degrees(360.0), epsilon = 2e-7);
77    /// ```
78    pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
79
80    /// A rotation of π radians.
81    /// Corresponds to a half-turn.
82    pub const PI: Self = Self {
83        cos: -1.0,
84        sin: 0.0,
85    };
86
87    /// A counterclockwise rotation of π/2 radians.
88    /// Corresponds to a counterclockwise quarter-turn.
89    pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
90
91    /// A counterclockwise rotation of π/3 radians.
92    /// Corresponds to a counterclockwise turn by 60°.
93    pub const FRAC_PI_3: Self = Self {
94        cos: 0.5,
95        sin: 0.866_025_4,
96    };
97
98    /// A counterclockwise rotation of π/4 radians.
99    /// Corresponds to a counterclockwise turn by 45°.
100    pub const FRAC_PI_4: Self = Self {
101        cos: core::f32::consts::FRAC_1_SQRT_2,
102        sin: core::f32::consts::FRAC_1_SQRT_2,
103    };
104
105    /// A counterclockwise rotation of π/6 radians.
106    /// Corresponds to a counterclockwise turn by 30°.
107    pub const FRAC_PI_6: Self = Self {
108        cos: 0.866_025_4,
109        sin: 0.5,
110    };
111
112    /// A counterclockwise rotation of π/8 radians.
113    /// Corresponds to a counterclockwise turn by 22.5°.
114    pub const FRAC_PI_8: Self = Self {
115        cos: 0.923_879_5,
116        sin: 0.382_683_43,
117    };
118
119    /// Creates a [`Rot2`] from a counterclockwise angle in radians.
120    /// A negative argument corresponds to a clockwise rotation.
121    ///
122    /// # Note
123    ///
124    /// Angles larger than or equal to 2π (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// # use bevy_math::Rot2;
130    /// # use approx::assert_relative_eq;
131    /// # use std::f32::consts::{FRAC_PI_2, PI};
132    ///
133    /// let rot1 = Rot2::radians(3.0 * FRAC_PI_2);
134    /// let rot2 = Rot2::radians(-FRAC_PI_2);
135    /// #[cfg(feature = "approx")]
136    /// assert_relative_eq!(rot1, rot2);
137    ///
138    /// let rot3 = Rot2::radians(PI);
139    /// #[cfg(feature = "approx")]
140    /// assert_relative_eq!(rot1 * rot1, rot3);
141    ///
142    /// // A rotation by 3π and 1π are the same
143    /// #[cfg(feature = "approx")]
144    /// assert_relative_eq!(Rot2::radians(3.0 * PI), Rot2::radians(PI));
145    /// ```
146    #[inline]
147    pub fn radians(radians: f32) -> Self {
148        let (sin, cos) = ops::sin_cos(radians);
149        Self::from_sin_cos(sin, cos)
150    }
151
152    /// Creates a [`Rot2`] from a counterclockwise angle in degrees.
153    /// A negative argument corresponds to a clockwise rotation.
154    ///
155    /// # Note
156    ///
157    /// Angles larger than or equal to 360° (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// # use bevy_math::Rot2;
163    /// # use approx::{assert_relative_eq, assert_abs_diff_eq};
164    ///
165    /// let rot1 = Rot2::degrees(270.0);
166    /// let rot2 = Rot2::degrees(-90.0);
167    /// #[cfg(feature = "approx")]
168    /// assert_relative_eq!(rot1, rot2);
169    ///
170    /// let rot3 = Rot2::degrees(180.0);
171    /// #[cfg(feature = "approx")]
172    /// assert_relative_eq!(rot1 * rot1, rot3);
173    ///
174    /// // A rotation by 365° and 5° are the same
175    /// #[cfg(feature = "approx")]
176    /// assert_abs_diff_eq!(Rot2::degrees(365.0), Rot2::degrees(5.0), epsilon = 2e-7);
177    /// ```
178    #[inline]
179    pub fn degrees(degrees: f32) -> Self {
180        Self::radians(degrees.to_radians())
181    }
182
183    /// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees.
184    /// A negative argument corresponds to a clockwise rotation.
185    ///
186    /// # Note
187    ///
188    /// Angles larger than or equal to 1 turn (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.
189    ///
190    /// # Example
191    ///
192    /// ```
193    /// # use bevy_math::Rot2;
194    /// # use approx::assert_relative_eq;
195    ///
196    /// let rot1 = Rot2::turn_fraction(0.75);
197    /// let rot2 = Rot2::turn_fraction(-0.25);
198    /// #[cfg(feature = "approx")]
199    /// assert_relative_eq!(rot1, rot2);
200    ///
201    /// let rot3 = Rot2::turn_fraction(0.5);
202    /// #[cfg(feature = "approx")]
203    /// assert_relative_eq!(rot1 * rot1, rot3);
204    ///
205    /// // A rotation by 1.5 turns and 0.5 turns are the same
206    /// #[cfg(feature = "approx")]
207    /// assert_relative_eq!(Rot2::turn_fraction(1.5), Rot2::turn_fraction(0.5));
208    /// ```
209    #[inline]
210    pub fn turn_fraction(fraction: f32) -> Self {
211        Self::radians(TAU * fraction)
212    }
213
214    /// Creates a [`Rot2`] from the sine and cosine of an angle.
215    ///
216    /// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
217    ///
218    /// # Panics
219    ///
220    /// Panics if `sin * sin + cos * cos != 1.0` when the `glam_assert` feature is enabled.
221    #[inline]
222    pub fn from_sin_cos(sin: f32, cos: f32) -> Self {
223        let rotation = Self { sin, cos };
224        debug_assert!(
225            rotation.is_normalized(),
226            "the given sine and cosine produce an invalid rotation"
227        );
228        rotation
229    }
230
231    /// Returns a corresponding rotation angle in radians in the `(-pi, pi]` range.
232    #[inline]
233    pub fn as_radians(self) -> f32 {
234        ops::atan2(self.sin, self.cos)
235    }
236
237    /// Returns a corresponding rotation angle in degrees in the `(-180, 180]` range.
238    #[inline]
239    pub fn as_degrees(self) -> f32 {
240        self.as_radians().to_degrees()
241    }
242
243    /// Returns a corresponding rotation angle as a fraction of a full 360 degree turn in the `(-0.5, 0.5]` range.
244    #[inline]
245    pub fn as_turn_fraction(self) -> f32 {
246        self.as_radians() / TAU
247    }
248
249    /// Returns the sine and cosine of the rotation angle.
250    #[inline]
251    pub const fn sin_cos(self) -> (f32, f32) {
252        (self.sin, self.cos)
253    }
254
255    /// Computes the length or norm of the complex number used to represent the rotation.
256    ///
257    /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
258    /// can be a result of incorrect construction or floating point error caused by
259    /// successive operations.
260    #[inline]
261    #[doc(alias = "norm")]
262    pub fn length(self) -> f32 {
263        Vec2::new(self.sin, self.cos).length()
264    }
265
266    /// Computes the squared length or norm of the complex number used to represent the rotation.
267    ///
268    /// This is generally faster than [`Rot2::length()`], as it avoids a square
269    /// root operation.
270    ///
271    /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
272    /// can be a result of incorrect construction or floating point error caused by
273    /// successive operations.
274    #[inline]
275    #[doc(alias = "norm2")]
276    pub fn length_squared(self) -> f32 {
277        Vec2::new(self.sin, self.cos).length_squared()
278    }
279
280    /// Computes `1.0 / self.length()`.
281    ///
282    /// For valid results, `self` must _not_ have a length of zero.
283    #[inline]
284    pub fn length_recip(self) -> f32 {
285        Vec2::new(self.sin, self.cos).length_recip()
286    }
287
288    /// Returns `self` with a length of `1.0` if possible, and `None` otherwise.
289    ///
290    /// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),
291    /// or if either of them is NaN or infinite.
292    ///
293    /// Note that [`Rot2`] should typically already be normalized by design.
294    /// Manual normalization is only needed when successive operations result in
295    /// accumulated floating point error, or if the rotation was constructed
296    /// with invalid values.
297    #[inline]
298    pub fn try_normalize(self) -> Option<Self> {
299        let recip = self.length_recip();
300        if recip.is_finite() && recip > 0.0 {
301            Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))
302        } else {
303            None
304        }
305    }
306
307    /// Returns `self` with a length of `1.0`.
308    ///
309    /// Note that [`Rot2`] should typically already be normalized by design.
310    /// Manual normalization is only needed when successive operations result in
311    /// accumulated floating point error, or if the rotation was constructed
312    /// with invalid values.
313    ///
314    /// # Panics
315    ///
316    /// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.
317    #[inline]
318    pub fn normalize(self) -> Self {
319        let length_recip = self.length_recip();
320        Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)
321    }
322
323    /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
324    /// Useful for preventing numerical error accumulation.
325    /// See [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for an example of when such error accumulation might occur.
326    #[inline]
327    pub fn fast_renormalize(self) -> Self {
328        let length_squared = self.length_squared();
329        // Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for more details.
330        let length_recip_approx = 0.5 * (3.0 - length_squared);
331        Rot2 {
332            sin: self.sin * length_recip_approx,
333            cos: self.cos * length_recip_approx,
334        }
335    }
336
337    /// Returns `true` if the rotation is neither infinite nor NaN.
338    #[inline]
339    pub const fn is_finite(self) -> bool {
340        self.sin.is_finite() && self.cos.is_finite()
341    }
342
343    /// Returns `true` if the rotation is NaN.
344    #[inline]
345    pub const fn is_nan(self) -> bool {
346        self.sin.is_nan() || self.cos.is_nan()
347    }
348
349    /// Returns whether `self` has a length of `1.0` or not.
350    ///
351    /// Uses a precision threshold of approximately `1e-4`.
352    #[inline]
353    pub fn is_normalized(self) -> bool {
354        // The allowed length is 1 +/- 1e-4, so the largest allowed
355        // squared length is (1 + 1e-4)^2 = 1.00020001, which makes
356        // the threshold for the squared length approximately 2e-4.
357        ops::abs(self.length_squared() - 1.0) <= 2e-4
358    }
359
360    /// Returns `true` if the rotation is near [`Rot2::IDENTITY`].
361    #[inline]
362    pub fn is_near_identity(self) -> bool {
363        // Same as `Quat::is_near_identity`, but using sine and cosine
364        let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;
365        self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin
366    }
367
368    /// Returns the angle in radians needed to make `self` and `other` coincide.
369    #[inline]
370    pub fn angle_to(self, other: Self) -> f32 {
371        (other * self.inverse()).as_radians()
372    }
373
374    /// Returns the inverse of the rotation. This is also the conjugate
375    /// of the unit complex number representing the rotation.
376    #[inline]
377    #[must_use]
378    #[doc(alias = "conjugate")]
379    pub const fn inverse(self) -> Self {
380        Self {
381            cos: self.cos,
382            sin: -self.sin,
383        }
384    }
385
386    /// Performs a linear interpolation between `self` and `rhs` based on
387    /// the value `s`, and normalizes the rotation afterwards.
388    ///
389    /// When `s == 0.0`, the result will be equal to `self`.
390    /// When `s == 1.0`, the result will be equal to `rhs`.
391    ///
392    /// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result
393    /// when the difference between the two rotations is small. At larger differences,
394    /// the result resembles a kind of ease-in-out effect.
395    ///
396    /// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.
397    ///
398    /// # Details
399    ///
400    /// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn
401    /// between the endpoints of the arc formed by `self` and `rhs` on a unit circle,
402    /// and normalizing the result afterwards.
403    ///
404    /// Note that if the angles are opposite like 0 and π, the line will pass through the origin,
405    /// and the resulting angle will always be either `self` or `rhs` depending on `s`.
406    /// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`
407    /// will be returned as a fallback.
408    ///
409    /// # Example
410    ///
411    /// ```
412    /// # use bevy_math::Rot2;
413    /// #
414    /// let rot1 = Rot2::IDENTITY;
415    /// let rot2 = Rot2::degrees(135.0);
416    ///
417    /// let result1 = rot1.nlerp(rot2, 1.0 / 3.0);
418    /// assert_eq!(result1.as_degrees(), 28.675055);
419    ///
420    /// let result2 = rot1.nlerp(rot2, 0.5);
421    /// assert_eq!(result2.as_degrees(), 67.5);
422    /// ```
423    #[inline]
424    pub fn nlerp(self, end: Self, s: f32) -> Self {
425        Self {
426            sin: self.sin.lerp(end.sin, s),
427            cos: self.cos.lerp(end.cos, s),
428        }
429        .try_normalize()
430        // Fall back to the start rotation.
431        // This can happen when `self` and `end` are opposite angles and `s == 0.5`,
432        // because the resulting rotation would be zero, which cannot be normalized.
433        .unwrap_or(self)
434    }
435
436    /// Performs a spherical linear interpolation between `self` and `end`
437    /// based on the value `s`.
438    ///
439    /// This corresponds to interpolating between the two angles at a constant angular velocity.
440    ///
441    /// When `s == 0.0`, the result will be equal to `self`.
442    /// When `s == 1.0`, the result will be equal to `rhs`.
443    ///
444    /// If you would like the rotation to have a kind of ease-in-out effect, consider
445    /// using the slightly more efficient [`nlerp`](Self::nlerp) instead.
446    ///
447    /// # Example
448    ///
449    /// ```
450    /// # use bevy_math::Rot2;
451    /// #
452    /// let rot1 = Rot2::IDENTITY;
453    /// let rot2 = Rot2::degrees(135.0);
454    ///
455    /// let result1 = rot1.slerp(rot2, 1.0 / 3.0);
456    /// assert_eq!(result1.as_degrees(), 45.0);
457    ///
458    /// let result2 = rot1.slerp(rot2, 0.5);
459    /// assert_eq!(result2.as_degrees(), 67.5);
460    /// ```
461    #[inline]
462    pub fn slerp(self, end: Self, s: f32) -> Self {
463        self * Self::radians(self.angle_to(end) * s)
464    }
465}
466
467impl From<f32> for Rot2 {
468    /// Creates a [`Rot2`] from a counterclockwise angle in radians.
469    fn from(rotation: f32) -> Self {
470        Self::radians(rotation)
471    }
472}
473
474impl From<Rot2> for Mat2 {
475    /// Creates a [`Mat2`] rotation matrix from a [`Rot2`].
476    fn from(rot: Rot2) -> Self {
477        Mat2::from_cols_array(&[rot.cos, rot.sin, -rot.sin, rot.cos])
478    }
479}
480
481impl core::ops::Mul for Rot2 {
482    type Output = Self;
483
484    fn mul(self, rhs: Self) -> Self::Output {
485        Self {
486            cos: self.cos * rhs.cos - self.sin * rhs.sin,
487            sin: self.sin * rhs.cos + self.cos * rhs.sin,
488        }
489    }
490}
491
492impl core::ops::MulAssign for Rot2 {
493    fn mul_assign(&mut self, rhs: Self) {
494        *self = *self * rhs;
495    }
496}
497
498impl core::ops::Mul<Vec2> for Rot2 {
499    type Output = Vec2;
500
501    /// Rotates a [`Vec2`] by a [`Rot2`].
502    fn mul(self, rhs: Vec2) -> Self::Output {
503        Vec2::new(
504            rhs.x * self.cos - rhs.y * self.sin,
505            rhs.x * self.sin + rhs.y * self.cos,
506        )
507    }
508}
509
510#[cfg(any(feature = "approx", test))]
511impl approx::AbsDiffEq for Rot2 {
512    type Epsilon = f32;
513    fn default_epsilon() -> f32 {
514        f32::EPSILON
515    }
516    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
517        self.cos.abs_diff_eq(&other.cos, epsilon) && self.sin.abs_diff_eq(&other.sin, epsilon)
518    }
519}
520
521#[cfg(any(feature = "approx", test))]
522impl approx::RelativeEq for Rot2 {
523    fn default_max_relative() -> f32 {
524        f32::EPSILON
525    }
526    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
527        self.cos.relative_eq(&other.cos, epsilon, max_relative)
528            && self.sin.relative_eq(&other.sin, epsilon, max_relative)
529    }
530}
531
532#[cfg(any(feature = "approx", test))]
533impl approx::UlpsEq for Rot2 {
534    fn default_max_ulps() -> u32 {
535        4
536    }
537    fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
538        self.cos.ulps_eq(&other.cos, epsilon, max_ulps)
539            && self.sin.ulps_eq(&other.sin, epsilon, max_ulps)
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use core::f32::consts::FRAC_PI_2;
546
547    use approx::assert_relative_eq;
548
549    use crate::{ops, Dir2, Mat2, Rot2, Vec2};
550
551    #[test]
552    fn creation() {
553        let rotation1 = Rot2::radians(FRAC_PI_2);
554        let rotation2 = Rot2::degrees(90.0);
555        let rotation3 = Rot2::from_sin_cos(1.0, 0.0);
556        let rotation4 = Rot2::turn_fraction(0.25);
557
558        // All three rotations should be equal
559        assert_relative_eq!(rotation1.sin, rotation2.sin);
560        assert_relative_eq!(rotation1.cos, rotation2.cos);
561        assert_relative_eq!(rotation1.sin, rotation3.sin);
562        assert_relative_eq!(rotation1.cos, rotation3.cos);
563        assert_relative_eq!(rotation1.sin, rotation4.sin);
564        assert_relative_eq!(rotation1.cos, rotation4.cos);
565
566        // The rotation should be 90 degrees
567        assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2);
568        assert_relative_eq!(rotation1.as_degrees(), 90.0);
569        assert_relative_eq!(rotation1.as_turn_fraction(), 0.25);
570    }
571
572    #[test]
573    fn rotate() {
574        let rotation = Rot2::degrees(90.0);
575
576        assert_relative_eq!(rotation * Vec2::X, Vec2::Y);
577        assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X);
578    }
579
580    #[test]
581    fn rotation_range() {
582        // the rotation range is `(-180, 180]` and the constructors
583        // normalize the rotations to that range
584        assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2));
585        assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0));
586        assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25));
587    }
588
589    #[test]
590    fn add() {
591        let rotation1 = Rot2::degrees(90.0);
592        let rotation2 = Rot2::degrees(180.0);
593
594        // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range
595        assert_eq!((rotation1 * rotation2).as_degrees(), -90.0);
596    }
597
598    #[test]
599    fn subtract() {
600        let rotation1 = Rot2::degrees(90.0);
601        let rotation2 = Rot2::degrees(45.0);
602
603        assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);
604
605        // This should be equivalent to the above
606        assert_relative_eq!(rotation2.angle_to(rotation1), core::f32::consts::FRAC_PI_4);
607    }
608
609    #[test]
610    fn length() {
611        let rotation = Rot2 {
612            sin: 10.0,
613            cos: 5.0,
614        };
615
616        assert_eq!(rotation.length_squared(), 125.0);
617        assert_eq!(rotation.length(), 11.18034);
618        assert!(ops::abs(rotation.normalize().length() - 1.0) < 10e-7);
619    }
620
621    #[test]
622    fn is_near_identity() {
623        assert!(!Rot2::radians(0.1).is_near_identity());
624        assert!(!Rot2::radians(-0.1).is_near_identity());
625        assert!(Rot2::radians(0.00001).is_near_identity());
626        assert!(Rot2::radians(-0.00001).is_near_identity());
627        assert!(Rot2::radians(0.0).is_near_identity());
628    }
629
630    #[test]
631    fn normalize() {
632        let rotation = Rot2 {
633            sin: 10.0,
634            cos: 5.0,
635        };
636        let normalized_rotation = rotation.normalize();
637
638        assert_eq!(normalized_rotation.sin, 0.89442724);
639        assert_eq!(normalized_rotation.cos, 0.44721362);
640
641        assert!(!rotation.is_normalized());
642        assert!(normalized_rotation.is_normalized());
643    }
644
645    #[test]
646    fn fast_renormalize() {
647        let rotation = Rot2 { sin: 1.0, cos: 0.5 };
648        let normalized_rotation = rotation.normalize();
649
650        let mut unnormalized_rot = rotation;
651        let mut renormalized_rot = rotation;
652        let mut initially_normalized_rot = normalized_rotation;
653        let mut fully_normalized_rot = normalized_rotation;
654
655        // Compute a 64x (=2⁶) multiple of the rotation.
656        for _ in 0..6 {
657            unnormalized_rot = unnormalized_rot * unnormalized_rot;
658            renormalized_rot = renormalized_rot * renormalized_rot;
659            initially_normalized_rot = initially_normalized_rot * initially_normalized_rot;
660            fully_normalized_rot = fully_normalized_rot * fully_normalized_rot;
661
662            renormalized_rot = renormalized_rot.fast_renormalize();
663            fully_normalized_rot = fully_normalized_rot.normalize();
664        }
665
666        assert!(!unnormalized_rot.is_normalized());
667
668        assert!(renormalized_rot.is_normalized());
669        assert!(fully_normalized_rot.is_normalized());
670
671        assert_relative_eq!(fully_normalized_rot, renormalized_rot, epsilon = 0.000001);
672        assert_relative_eq!(
673            fully_normalized_rot,
674            unnormalized_rot.normalize(),
675            epsilon = 0.000001
676        );
677        assert_relative_eq!(
678            fully_normalized_rot,
679            initially_normalized_rot.normalize(),
680            epsilon = 0.000001
681        );
682    }
683
684    #[test]
685    fn try_normalize() {
686        // Valid
687        assert!(Rot2 {
688            sin: 10.0,
689            cos: 5.0,
690        }
691        .try_normalize()
692        .is_some());
693
694        // NaN
695        assert!(Rot2 {
696            sin: f32::NAN,
697            cos: 5.0,
698        }
699        .try_normalize()
700        .is_none());
701
702        // Zero
703        assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());
704
705        // Non-finite
706        assert!(Rot2 {
707            sin: f32::INFINITY,
708            cos: 5.0,
709        }
710        .try_normalize()
711        .is_none());
712    }
713
714    #[test]
715    fn nlerp() {
716        let rot1 = Rot2::IDENTITY;
717        let rot2 = Rot2::degrees(135.0);
718
719        assert_eq!(rot1.nlerp(rot2, 1.0 / 3.0).as_degrees(), 28.675055);
720        assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
721        assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 67.5);
722        assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees(), 135.0);
723
724        let rot1 = Rot2::IDENTITY;
725        let rot2 = Rot2::from_sin_cos(0.0, -1.0);
726
727        assert!(rot1.nlerp(rot2, 1.0 / 3.0).is_near_identity());
728        assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
729        // At 0.5, there is no valid rotation, so the fallback is the original angle.
730        assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0);
731        assert_eq!(ops::abs(rot1.nlerp(rot2, 1.0).as_degrees()), 180.0);
732    }
733
734    #[test]
735    fn slerp() {
736        let rot1 = Rot2::IDENTITY;
737        let rot2 = Rot2::degrees(135.0);
738
739        assert_eq!(rot1.slerp(rot2, 1.0 / 3.0).as_degrees(), 45.0);
740        assert!(rot1.slerp(rot2, 0.0).is_near_identity());
741        assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 67.5);
742        assert_eq!(rot1.slerp(rot2, 1.0).as_degrees(), 135.0);
743
744        let rot1 = Rot2::IDENTITY;
745        let rot2 = Rot2::from_sin_cos(0.0, -1.0);
746
747        assert!(ops::abs(rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0) < 10e-6);
748        assert!(rot1.slerp(rot2, 0.0).is_near_identity());
749        assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0);
750        assert_eq!(ops::abs(rot1.slerp(rot2, 1.0).as_degrees()), 180.0);
751    }
752
753    #[test]
754    fn rotation_matrix() {
755        let rotation = Rot2::degrees(90.0);
756        let matrix: Mat2 = rotation.into();
757
758        // Check that the matrix is correct.
759        assert_relative_eq!(matrix.x_axis, Vec2::Y);
760        assert_relative_eq!(matrix.y_axis, Vec2::NEG_X);
761
762        // Check that the matrix rotates vectors correctly.
763        assert_relative_eq!(matrix * Vec2::X, Vec2::Y);
764        assert_relative_eq!(matrix * Vec2::Y, Vec2::NEG_X);
765        assert_relative_eq!(matrix * Vec2::NEG_X, Vec2::NEG_Y);
766        assert_relative_eq!(matrix * Vec2::NEG_Y, Vec2::X);
767    }
768}