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}