Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/rotation2d.rs
6595 views
1
use core::f32::consts::TAU;
2
3
use glam::FloatExt;
4
5
use crate::{
6
ops,
7
prelude::{Mat2, Vec2},
8
};
9
10
#[cfg(feature = "bevy_reflect")]
11
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
13
use 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")]
52
pub 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
63
impl Default for Rot2 {
64
fn default() -> Self {
65
Self::IDENTITY
66
}
67
}
68
69
impl 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
467
impl 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
474
impl 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
481
impl 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
492
impl core::ops::MulAssign for Rot2 {
493
fn mul_assign(&mut self, rhs: Self) {
494
*self = *self * rhs;
495
}
496
}
497
498
impl 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))]
511
impl 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))]
522
impl 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))]
533
impl 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)]
544
mod 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
}
769
770