avian3d/math/drot2.rs
1use core::f64::consts::TAU;
2
3use crate::ops;
4use bevy_math::{DMat2, DVec2, prelude::*};
5
6use bevy::reflect::prelude::*;
7#[cfg(feature = "serialize")]
8use bevy::reflect::{ReflectDeserialize, ReflectSerialize};
9
10/// A double-precision version of [`Rot2`].
11#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
12#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
14#[doc(alias = "drotation", alias = "drotation2d", alias = "drotation_2d")]
15pub struct DRot2 {
16 /// The cosine of the rotation angle.
17 ///
18 /// This is the real part of the unit complex number representing the rotation.
19 pub cos: f64,
20 /// The sine of the rotation angle.
21 ///
22 /// This is the imaginary part of the unit complex number representing the rotation.
23 pub sin: f64,
24}
25
26impl Default for DRot2 {
27 fn default() -> Self {
28 Self::IDENTITY
29 }
30}
31
32impl DRot2 {
33 /// The identity rotation.
34 pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
35
36 /// A rotation of π radians.
37 /// Corresponds to a half-turn.
38 pub const PI: Self = Self {
39 cos: -1.0,
40 sin: 0.0,
41 };
42
43 /// A counterclockwise rotation of π/2 radians.
44 /// Corresponds to a counterclockwise quarter-turn.
45 pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
46
47 /// A counterclockwise rotation of π/3 radians.
48 /// Corresponds to a counterclockwise turn by 60°.
49 pub const FRAC_PI_3: Self = Self {
50 cos: 0.5,
51 sin: 0.866_025_4,
52 };
53
54 /// A counterclockwise rotation of π/4 radians.
55 /// Corresponds to a counterclockwise turn by 45°.
56 pub const FRAC_PI_4: Self = Self {
57 cos: core::f64::consts::FRAC_1_SQRT_2,
58 sin: core::f64::consts::FRAC_1_SQRT_2,
59 };
60
61 /// A counterclockwise rotation of π/6 radians.
62 /// Corresponds to a counterclockwise turn by 30°.
63 pub const FRAC_PI_6: Self = Self {
64 cos: 0.866_025_4,
65 sin: 0.5,
66 };
67
68 /// A counterclockwise rotation of π/8 radians.
69 /// Corresponds to a counterclockwise turn by 22.5°.
70 pub const FRAC_PI_8: Self = Self {
71 cos: 0.923_879_5,
72 sin: 0.382_683_43,
73 };
74
75 /// Creates a [`DRot2`] from a counterclockwise angle in radians.
76 /// A negative argument corresponds to a clockwise rotation.
77 ///
78 /// # Note
79 ///
80 /// Angles larger than or equal to 2π (in either direction) loop around to smaller rotations,
81 /// since a full rotation returns an object to its starting orientation.
82 #[inline]
83 pub fn radians(radians: f64) -> Self {
84 #[cfg(feature = "enhanced-determinism")]
85 let (sin, cos) = (libm::sin(radians), libm::cos(radians));
86 #[cfg(not(feature = "enhanced-determinism"))]
87 let (sin, cos) = radians.sin_cos();
88
89 Self::from_sin_cos(sin, cos)
90 }
91
92 /// Creates a [`DRot2`] from a counterclockwise angle in degrees.
93 /// A negative argument corresponds to a clockwise rotation.
94 ///
95 /// # Note
96 ///
97 /// Angles larger than or equal to 360° (in either direction) loop around to smaller rotations,
98 /// since a full rotation returns an object to its starting orientation.
99 #[inline]
100 pub fn degrees(degrees: f64) -> Self {
101 Self::radians(degrees.to_radians())
102 }
103
104 /// Creates a [`DRot2`] from a counterclockwise fraction of a full turn of 360 degrees.
105 /// A negative argument corresponds to a clockwise rotation.
106 ///
107 /// # Note
108 ///
109 /// Angles larger than or equal to 1 turn (in either direction) loop around to smaller rotations,
110 /// since a full rotation returns an object to its starting orientation.
111 #[inline]
112 pub fn turn_fraction(fraction: f64) -> Self {
113 Self::radians(TAU * fraction)
114 }
115
116 /// Creates a [`DRot2`] from the sine and cosine of an angle.
117 ///
118 /// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
119 ///
120 /// # Panics
121 ///
122 /// Panics if `sin * sin + cos * cos != 1.0` with debug assertions enabled.
123 #[inline]
124 pub fn from_sin_cos(sin: f64, cos: f64) -> Self {
125 let rotation = Self { sin, cos };
126 debug_assert!(
127 rotation.is_normalized(),
128 "the given sine and cosine produce an invalid rotation"
129 );
130 rotation
131 }
132
133 /// Returns a corresponding rotation angle in radians in the `(-pi, pi]` range.
134 #[inline]
135 pub fn as_radians(self) -> f64 {
136 #[cfg(feature = "enhanced-determinism")]
137 {
138 libm::atan2(self.sin, self.cos)
139 }
140 #[cfg(not(feature = "enhanced-determinism"))]
141 {
142 f64::atan2(self.sin, self.cos)
143 }
144 }
145
146 /// Returns a corresponding rotation angle in degrees in the `(-180, 180]` range.
147 #[inline]
148 pub fn as_degrees(self) -> f64 {
149 self.as_radians().to_degrees()
150 }
151
152 /// Returns a corresponding rotation angle as a fraction of a full 360 degree turn in the `(-0.5, 0.5]` range.
153 #[inline]
154 pub fn as_turn_fraction(self) -> f64 {
155 self.as_radians() / TAU
156 }
157
158 /// Returns the sine and cosine of the rotation angle.
159 #[inline]
160 pub const fn sin_cos(self) -> (f64, f64) {
161 (self.sin, self.cos)
162 }
163
164 /// Computes the length or norm of the complex number used to represent the rotation.
165 ///
166 /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
167 /// can be a result of incorrect construction or floating point error caused by
168 /// successive operations.
169 #[inline]
170 #[doc(alias = "norm")]
171 pub fn length(self) -> f64 {
172 DVec2::new(self.sin, self.cos).length()
173 }
174
175 /// Computes the squared length or norm of the complex number used to represent the rotation.
176 ///
177 /// This is generally faster than [`DRot2::length()`], as it avoids a square
178 /// root operation.
179 ///
180 /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
181 /// can be a result of incorrect construction or floating point error caused by
182 /// successive operations.
183 #[inline]
184 #[doc(alias = "norm2")]
185 pub fn length_squared(self) -> f64 {
186 DVec2::new(self.sin, self.cos).length_squared()
187 }
188
189 /// Computes `1.0 / self.length()`.
190 ///
191 /// For valid results, `self` must _not_ have a length of zero.
192 #[inline]
193 pub fn length_recip(self) -> f64 {
194 DVec2::new(self.sin, self.cos).length_recip()
195 }
196
197 /// Returns `self` with a length of `1.0` if possible, and `None` otherwise.
198 ///
199 /// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),
200 /// or if either of them is NaN or infinite.
201 ///
202 /// Note that [`DRot2`] should typically already be normalized by design.
203 /// Manual normalization is only needed when successive operations result in
204 /// accumulated floating point error, or if the rotation was constructed
205 /// with invalid values.
206 #[inline]
207 pub fn try_normalize(self) -> Option<Self> {
208 let recip = self.length_recip();
209 if recip.is_finite() && recip > 0.0 {
210 Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))
211 } else {
212 None
213 }
214 }
215
216 /// Returns `self` with a length of `1.0`.
217 ///
218 /// Note that [`DRot2`] should typically already be normalized by design.
219 /// Manual normalization is only needed when successive operations result in
220 /// accumulated floating point error, or if the rotation was constructed
221 /// with invalid values.
222 ///
223 /// # Panics
224 ///
225 /// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.
226 #[inline]
227 pub fn normalize(self) -> Self {
228 let length_recip = self.length_recip();
229 Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)
230 }
231
232 /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
233 /// Useful for preventing numerical error accumulation.
234 #[inline]
235 pub fn fast_renormalize(self) -> Self {
236 let length_squared = self.length_squared();
237 // Based on a Taylor approximation of the inverse square root
238 let length_recip_approx = 0.5 * (3.0 - length_squared);
239 DRot2 {
240 sin: self.sin * length_recip_approx,
241 cos: self.cos * length_recip_approx,
242 }
243 }
244
245 /// Returns `true` if the rotation is neither infinite nor NaN.
246 #[inline]
247 pub const fn is_finite(self) -> bool {
248 self.sin.is_finite() && self.cos.is_finite()
249 }
250
251 /// Returns `true` if the rotation is NaN.
252 #[inline]
253 pub const fn is_nan(self) -> bool {
254 self.sin.is_nan() || self.cos.is_nan()
255 }
256
257 /// Returns whether `self` has a length of `1.0` or not.
258 ///
259 /// Uses a precision threshold of approximately `1e-4`.
260 #[inline]
261 pub fn is_normalized(self) -> bool {
262 // The allowed length is 1 +/- 1e-4, so the largest allowed
263 // squared length is (1 + 1e-4)^2 = 1.00020001, which makes
264 // the threshold for the squared length approximately 2e-4.
265 (self.length_squared() - 1.0).abs() <= 2e-4
266 }
267
268 /// Returns `true` if the rotation is near [`DRot2::IDENTITY`].
269 #[inline]
270 pub fn is_near_identity(self) -> bool {
271 // Same as `Quat::is_near_identity`, but using sine and cosine
272 let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;
273 self.cos > 0.0 && self.sin.abs() < threshold_angle_sin
274 }
275
276 /// Returns the angle in radians needed to make `self` and `other` coincide.
277 #[inline]
278 pub fn angle_to(self, other: Self) -> f64 {
279 (other * self.inverse()).as_radians()
280 }
281
282 /// Returns the inverse of the rotation. This is also the conjugate
283 /// of the unit complex number representing the rotation.
284 #[inline]
285 #[must_use]
286 #[doc(alias = "conjugate")]
287 pub const fn inverse(self) -> Self {
288 Self {
289 cos: self.cos,
290 sin: -self.sin,
291 }
292 }
293
294 /// Performs a linear interpolation between `self` and `end` based on
295 /// the value `s`, and normalizes the rotation afterwards.
296 ///
297 /// When `s == 0.0`, the result will be equal to `self`.
298 /// When `s == 1.0`, the result will be equal to `end`.
299 ///
300 /// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result
301 /// when the difference between the two rotations is small. At larger differences,
302 /// the result resembles a kind of ease-in-out effect.
303 ///
304 /// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.
305 ///
306 /// # Details
307 ///
308 /// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn
309 /// between the endpoints of the arc formed by `self` and `end` on a unit circle,
310 /// and normalizing the result afterwards.
311 ///
312 /// Note that if the angles are opposite like 0 and π, the line will pass through the origin,
313 /// and the resulting angle will always be either `self` or `end` depending on `s`.
314 /// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`
315 /// will be returned as a fallback.
316 #[inline]
317 pub fn nlerp(self, end: Self, s: f64) -> Self {
318 Self {
319 sin: self.sin.lerp(end.sin, s),
320 cos: self.cos.lerp(end.cos, s),
321 }
322 .try_normalize()
323 // Fall back to the start rotation.
324 // This can happen when `self` and `end` are opposite angles and `s == 0.5`,
325 // because the resulting rotation would be zero, which cannot be normalized.
326 .unwrap_or(self)
327 }
328
329 /// Performs a spherical linear interpolation between `self` and `end`
330 /// based on the value `s`.
331 ///
332 /// This corresponds to interpolating between the two angles at a constant angular velocity.
333 ///
334 /// When `s == 0.0`, the result will be equal to `self`.
335 /// When `s == 1.0`, the result will be equal to `end`.
336 ///
337 /// If you would like the rotation to have a kind of ease-in-out effect, consider
338 /// using the slightly more efficient [`nlerp`](Self::nlerp) instead.
339 #[inline]
340 pub fn slerp(self, end: Self, s: f64) -> Self {
341 self * Self::radians(self.angle_to(end) * s)
342 }
343}
344
345impl From<f64> for DRot2 {
346 /// Creates a [`DRot2`] from a counterclockwise angle in radians.
347 fn from(rotation: f64) -> Self {
348 Self::radians(rotation)
349 }
350}
351
352impl From<DRot2> for DMat2 {
353 /// Creates a [`DMat2`] rotation matrix from a [`DRot2`].
354 fn from(rot: DRot2) -> Self {
355 DMat2::from_cols_array(&[rot.cos, rot.sin, -rot.sin, rot.cos])
356 }
357}
358
359impl core::ops::Mul for DRot2 {
360 type Output = Self;
361
362 fn mul(self, rhs: Self) -> Self::Output {
363 Self {
364 cos: self.cos * rhs.cos - self.sin * rhs.sin,
365 sin: self.sin * rhs.cos + self.cos * rhs.sin,
366 }
367 }
368}
369
370impl core::ops::MulAssign for DRot2 {
371 fn mul_assign(&mut self, rhs: Self) {
372 *self = *self * rhs;
373 }
374}
375
376impl core::ops::Mul<DVec2> for DRot2 {
377 type Output = DVec2;
378
379 /// Rotates a [`DVec2`] by a [`DRot2`].
380 fn mul(self, rhs: DVec2) -> Self::Output {
381 DVec2::new(
382 rhs.x * self.cos - rhs.y * self.sin,
383 rhs.x * self.sin + rhs.y * self.cos,
384 )
385 }
386}