angulus/
angle.rs

1use core::fmt::Debug;
2use core::iter::Sum;
3use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
4
5use crate::float::Float;
6use crate::macros::{forward_ref_binop, forward_ref_op_assign, forward_ref_unop};
7use crate::AngleUnbounded;
8
9/// Represents a point on the circle as a unit-agnostic angle.
10///
11/// The parameter `F` is the floating-point type used to store the value.
12///
13/// # Behaviour
14///
15/// Two different values of the same point on the circle are the same [`Angle`].
16///
17/// ```
18/// # use angulus::Angle;
19/// let a = Angle::from_degrees(90.0);
20/// let b = Angle::from_degrees(450.0);
21///
22/// assert_eq!(a, b);
23/// ```
24///
25/// # Why doesn't it implement [`PartialOrd`] ?
26///
27/// Because [`Angle`]s represent points on the circle (i.e., not a numerical value), they cannot be ordered.
28///
29/// # The `NaN` angle
30///
31/// An angle can be `NaN` in the following cases:
32///
33/// - Create an angle from a non-finite value;
34/// ```
35/// # use angulus::Angle;
36/// let a = Angle::from_radians(f32::INFINITY);
37/// assert!(a.is_nan());
38///
39/// let b = Angle::from_radians(f32::NAN);
40/// assert!(b.is_nan());
41/// ```
42/// - Doing an operation that result into a non-finite value;
43/// ```
44/// # use angulus::Angle;
45/// let a = Angle::DEG_90;
46/// let b = a / 0.0;
47///
48/// assert!(b.is_nan());
49/// ```
50#[derive(Copy, Clone, PartialEq, Eq, Hash)]
51#[repr(transparent)]
52pub struct Angle<F> {
53    radians: F,
54}
55
56//-------------------------------------------------------------------
57// Const
58//-------------------------------------------------------------------
59
60impl<F: Float> Angle<F> {
61    /// The angle of value zero.
62    pub const ZERO: Self = Angle::from_radians_unchecked(F::ZERO);
63
64    /// [Machine epsilon] value for [`Angle`].
65    ///
66    /// [Machine epsilon]: https://en.wikipedia.org/wiki/Machine_epsilon
67    pub const EPSILON: Self = Angle::from_radians_unchecked(F::DOUBLE_EPSILON);
68}
69
70impl<F: Float> Angle<F> {
71    /// The angle of π radians.
72    pub const RAD_PI: Self = Angle::from_radians_unchecked(F::PI);
73    /// The angle of π/2 radians.
74    pub const RAD_FRAC_PI_2: Self = Angle::from_radians_unchecked(F::FRAC_PI_2);
75    /// The angle of π/3 radians.
76    pub const RAD_FRAC_PI_3: Self = Angle::from_radians_unchecked(F::FRAC_PI_3);
77    /// The angle of π/4 radians.
78    pub const RAD_FRAC_PI_4: Self = Angle::from_radians_unchecked(F::FRAC_PI_4);
79    /// The angle of π/6 radians.
80    pub const RAD_FRAC_PI_6: Self = Angle::from_radians_unchecked(F::FRAC_PI_6);
81    /// The angle of π/8 radians.
82    pub const RAD_FRAC_PI_8: Self = Angle::from_radians_unchecked(F::FRAC_PI_8);
83}
84
85impl<F: Float> Angle<F> {
86    /// The angle of 180°.
87    pub const DEG_180: Self = Self::RAD_PI;
88    /// The angle of 90°.
89    pub const DEG_90: Self = Self::RAD_FRAC_PI_2;
90    /// The angle of 60°.
91    pub const DEG_60: Self = Self::RAD_FRAC_PI_3;
92    /// The angle of 45°.
93    pub const DEG_45: Self = Self::RAD_FRAC_PI_4;
94    /// The angle of 30°.
95    pub const DEG_30: Self = Self::RAD_FRAC_PI_6;
96    /// The angle of 22.5°.
97    pub const DEG_22_5: Self = Self::RAD_FRAC_PI_8;
98}
99
100impl<F: Float> Angle<F> {
101    /// The angle of a half of a circle (1/2 turns).
102    pub const HALF: Self = Self::RAD_PI;
103    /// The angle of a quarter of a circle (1/4 turns).
104    pub const QUARTER: Self = Self::RAD_FRAC_PI_2;
105    /// The angle of a sixth of a circle (1/6 turns).
106    pub const SIXTH: Self = Self::RAD_FRAC_PI_3;
107    ///  The angle of a eighth of a circle (1/8 turns).
108    pub const EIGHTH: Self = Self::RAD_FRAC_PI_4;
109    ///  The angle of a twelfth of a circle (1/12 turns).
110    pub const TWELFTH: Self = Self::RAD_FRAC_PI_6;
111    ///  The angle of a sixteenth of a circle (1/16 turns).
112    pub const SIXTEENTH: Self = Self::RAD_FRAC_PI_8;
113}
114
115impl<F: Float> Angle<F> {
116    /// The angle of 200g.
117    pub const GRAD_200: Self = Self::RAD_PI;
118    /// The angle of 100g.
119    pub const GRAD_100: Self = Self::RAD_FRAC_PI_2;
120    /// The angle of 66.6g.
121    pub const GRAD_66_6: Self = Self::RAD_FRAC_PI_3;
122    ///  The angle of 50g.
123    pub const GRAD_50: Self = Self::RAD_FRAC_PI_4;
124    ///  The angle of 33.3g.
125    pub const GRAD_33_3: Self = Self::RAD_FRAC_PI_6;
126    ///  The angle of 25g.
127    pub const GRAD_25: Self = Self::RAD_FRAC_PI_8;
128}
129
130//-------------------------------------------------------------------
131// Standard traits
132//-------------------------------------------------------------------
133
134impl<F: Debug> Debug for Angle<F> {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        f.debug_tuple("Angle").field(&self.radians).finish()
137    }
138}
139
140impl<F: Float> Default for Angle<F> {
141    #[inline]
142    fn default() -> Self {
143        Self::ZERO
144    }
145}
146
147//-------------------------------------------------------------------
148// Ctor
149//-------------------------------------------------------------------
150
151impl<F> Angle<F> {
152    /// Creates a new angle from a value in radians assuming it is already in
153    /// the the range `(-π, π]`.
154    #[inline]
155    pub const fn from_radians_unchecked(radians: F) -> Self {
156        Self { radians }
157    }
158}
159
160impl<F: Float> Angle<F> {
161    /// Creates a new angle from a value in radians assuming it is already in
162    /// the the range `[-2π, 2π]`.
163    #[inline]
164    fn from_radians_partially_unchecked(radians: F) -> Self {
165        debug_assert!(radians.is_nan() || (-F::TAU <= radians && radians <= F::TAU));
166        let radians = if radians > F::PI {
167            radians - F::TAU
168        } else if radians <= -F::PI {
169            radians + F::TAU
170        } else {
171            radians
172        };
173        Self::from_radians_unchecked(radians)
174    }
175
176    /// Creates a new angle from a value in radians.
177    #[inline]
178    pub fn from_radians(radians: F) -> Self {
179        Self::from_radians_partially_unchecked(radians % F::TAU)
180    }
181
182    /// Creates a new angle from a value in degrees.
183    #[inline]
184    pub fn from_degrees(degrees: F) -> Self {
185        Self::from_radians(degrees * F::DEG_TO_RAD)
186    }
187
188    /// Creates a new angle from a value in turns.
189    #[inline]
190    pub fn from_turns(turns: F) -> Self {
191        // NOTE: to avoid `NaN` when handling big values, we have to operate the REM before
192        // converting the value into radians.
193        Self::from_radians_partially_unchecked((turns % F::ONE) * F::TURNS_TO_RAD)
194    }
195
196    /// Creates a new angle from a value in gradians.
197    #[inline]
198    pub fn from_gradians(gradians: F) -> Self {
199        Self::from_radians(gradians * F::GRAD_TO_RAD)
200    }
201}
202
203//-------------------------------------------------------------------
204// Getters
205//-------------------------------------------------------------------
206
207impl<F: Copy> Angle<F> {
208    /// The value of the angle in radians.
209    ///
210    /// This value is in the range `(-π, π]`.
211    #[must_use = "this returns the result of the operation, without modifying the original"]
212    #[inline]
213    pub const fn to_radians(self) -> F {
214        self.radians
215    }
216}
217
218impl<F: Float> Angle<F> {
219    /// The value of the angle in degrees.
220    ///
221    /// This value is in the range `(-180, 180]`.
222    #[must_use = "this returns the result of the operation, without modifying the original"]
223    #[inline]
224    pub fn to_degrees(self) -> F {
225        self.radians * F::RAD_TO_DEG
226    }
227
228    /// The value of the angle in turns.
229    ///
230    /// This value is in the range `(-0.5, 0.5]`.
231    #[must_use = "this returns the result of the operation, without modifying the original"]
232    #[inline]
233    pub fn to_turns(self) -> F {
234        self.radians * F::RAD_TO_TURNS
235    }
236
237    /// The value of the angle in gradians.
238    ///
239    /// This value is in the range `(-200, 200]`.
240    #[must_use = "this returns the result of the operation, without modifying the original"]
241    #[inline]
242    pub fn to_gradians(self) -> F {
243        self.radians * F::RAD_TO_GRAD
244    }
245}
246
247impl<F: Float> Angle<F> {
248    /// Returns `true` if this angle is NaN.
249    ///
250    /// See [`Angle` documentation][Angle] for more details.
251    #[must_use]
252    #[inline]
253    pub fn is_nan(self) -> bool {
254        self.radians.is_nan()
255    }
256}
257
258//-------------------------------------------------------------------
259// Angle conversion
260//-------------------------------------------------------------------
261
262impl<F: Copy> Angle<F> {
263    /// Converts this angle into an unbounded angle in [the main range](crate#the-main-range).
264    #[must_use = "this returns the result of the operation, without modifying the original"]
265    #[inline]
266    pub const fn to_unbounded(self) -> AngleUnbounded<F> {
267        AngleUnbounded::from_radians(self.radians)
268    }
269}
270
271impl<F: Float> From<AngleUnbounded<F>> for Angle<F> {
272    #[inline]
273    fn from(angle: AngleUnbounded<F>) -> Self {
274        Self::from_radians(angle.to_radians())
275    }
276}
277
278//-------------------------------------------------------------------
279// Floating point type conversion
280//-------------------------------------------------------------------
281
282impl Angle<f32> {
283    /// Converts the floating point type to [`f64`].
284    #[must_use = "this returns the result of the operation, without modifying the original"]
285    #[inline]
286    pub fn to_f64(self) -> Angle<f64> {
287        let radians = f64::from(self.radians);
288        debug_assert!(
289            radians.is_nan()
290                || (-core::f64::consts::PI < radians && radians <= core::f64::consts::PI)
291        );
292        // Notes: f32 to f64 conversion is lossless, we don't need to check the range.
293        Angle::from_radians_unchecked(radians)
294    }
295}
296
297impl Angle<f64> {
298    /// Converts the floating point type to [`f32`].
299    #[must_use = "this returns the result of the operation, without modifying the original"]
300    #[inline]
301    pub fn to_f32(self) -> Angle<f32> {
302        #[allow(clippy::cast_possible_truncation)]
303        let radians = self.radians as f32;
304        Angle::from_radians(radians)
305    }
306}
307
308impl From<Angle<f64>> for Angle<f32> {
309    #[inline]
310    fn from(value: Angle<f64>) -> Self {
311        value.to_f32()
312    }
313}
314
315impl From<Angle<f32>> for Angle<f64> {
316    #[inline]
317    fn from(value: Angle<f32>) -> Self {
318        value.to_f64()
319    }
320}
321
322//-------------------------------------------------------------------
323// Maths
324//-------------------------------------------------------------------
325
326#[cfg(any(feature = "std", feature = "libm"))]
327impl<F: crate::float::FloatMath> Angle<F> {
328    /// Computes the sine.
329    #[must_use = "this returns the result of the operation, without modifying the original"]
330    #[inline]
331    pub fn sin(self) -> F {
332        self.radians.sin()
333    }
334
335    /// Computes the cosine.
336    #[must_use = "this returns the result of the operation, without modifying the original"]
337    #[inline]
338    pub fn cos(self) -> F {
339        self.radians.cos()
340    }
341
342    /// Computes the tangent.
343    #[must_use = "this returns the result of the operation, without modifying the original"]
344    #[inline]
345    pub fn tan(self) -> F {
346        self.radians.tan()
347    }
348
349    /// Simultaneously computes the sine and cosine. Returns `(sin(x), cos(x))`.
350    #[must_use = "this returns the result of the operation, without modifying the original"]
351    #[inline]
352    pub fn sin_cos(self) -> (F, F) {
353        self.radians.sin_cos()
354    }
355}
356
357//-------------------------------------------------------------------
358// Ops
359//-------------------------------------------------------------------
360
361impl<F: Float> Add for Angle<F> {
362    type Output = Self;
363
364    #[inline]
365    fn add(self, rhs: Self) -> Self::Output {
366        Self::from_radians(self.radians + rhs.radians)
367    }
368}
369
370forward_ref_binop!(impl<F: Float> Add, add for Angle<F>, Angle<F>);
371
372impl<F: Float> AddAssign for Angle<F> {
373    #[inline]
374    fn add_assign(&mut self, rhs: Self) {
375        *self = *self + rhs;
376    }
377}
378
379forward_ref_op_assign!(impl<F: Float> AddAssign, add_assign for Angle<F>, Angle<F>);
380
381impl<F: Float> Sub for Angle<F> {
382    type Output = Self;
383
384    #[inline]
385    fn sub(self, rhs: Self) -> Self::Output {
386        Self::from_radians(self.radians - rhs.radians)
387    }
388}
389
390forward_ref_binop!(impl<F: Float> Sub, sub for Angle<F>, Angle<F>);
391
392impl<F: Float> SubAssign for Angle<F> {
393    #[inline]
394    fn sub_assign(&mut self, rhs: Self) {
395        *self = *self - rhs;
396    }
397}
398
399forward_ref_op_assign!(impl<F: Float> SubAssign, sub_assign for Angle<F>, Angle<F>);
400
401impl<F: Float> Mul<F> for Angle<F> {
402    type Output = Self;
403
404    #[inline]
405    fn mul(self, rhs: F) -> Self::Output {
406        Self::from_radians(self.radians * rhs)
407    }
408}
409
410forward_ref_binop!(impl<F: Float> Mul, mul for Angle<F>, F);
411
412impl Mul<Angle<f32>> for f32 {
413    type Output = Angle<f32>;
414
415    #[inline]
416    fn mul(self, rhs: Angle<f32>) -> Self::Output {
417        rhs * self
418    }
419}
420
421forward_ref_binop!(impl Mul, mul for f32, Angle<f32>);
422
423impl Mul<Angle<f64>> for f64 {
424    type Output = Angle<f64>;
425
426    #[inline]
427    fn mul(self, rhs: Angle<f64>) -> Self::Output {
428        rhs * self
429    }
430}
431
432forward_ref_binop!(impl Mul, mul for f64, Angle<f64>);
433
434impl<F: Float> MulAssign<F> for Angle<F> {
435    #[inline]
436    fn mul_assign(&mut self, rhs: F) {
437        *self = *self * rhs;
438    }
439}
440
441forward_ref_op_assign!(impl<F: Float> MulAssign, mul_assign for Angle<F>, F);
442
443impl<F: Float> Div<F> for Angle<F> {
444    type Output = Self;
445
446    #[inline]
447    fn div(self, rhs: F) -> Self::Output {
448        Self::from_radians(self.radians / rhs)
449    }
450}
451
452forward_ref_binop!(impl<F: Float> Div, div for Angle<F>, F);
453
454impl<F: Float> DivAssign<F> for Angle<F> {
455    #[inline]
456    fn div_assign(&mut self, rhs: F) {
457        *self = *self / rhs;
458    }
459}
460
461forward_ref_op_assign!(impl<F: Float> DivAssign, div_assign for Angle<F>, F);
462
463impl<F: Float> Neg for Angle<F> {
464    type Output = Self;
465
466    #[inline]
467    fn neg(self) -> Self::Output {
468        debug_assert!(self.radians.is_nan() || (-F::PI < self.radians && self.radians <= F::PI));
469        if self.radians == F::PI {
470            self
471        } else {
472            Self::from_radians_unchecked(-self.radians)
473        }
474    }
475}
476
477forward_ref_unop!(impl<F: Float> Neg, neg for Angle<F>);
478
479impl<F: Float + Sum> Sum for Angle<F> {
480    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
481        Angle::from_radians(iter.map(|x| x.radians).sum())
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use float_eq::assert_float_eq;
488
489    use crate::{Angle, Angle32};
490
491    #[test]
492    fn angle_pi_eq_neg_pi() {
493        assert_eq!(
494            Angle::from_radians(core::f32::consts::PI),
495            Angle::from_radians(-core::f32::consts::PI),
496        );
497    }
498
499    #[test]
500    fn angle_sum_is_accurate() {
501        const ANGLES: [f32; 20] = [
502            0.711_889,
503            0.612_456_56,
504            -1.165_211_3,
505            -1.452_307,
506            -0.587_785_5,
507            0.593_006_5,
508            0.012_860_533,
509            -1.142_349_6,
510            -1.302_776_9,
511            0.510_909_6,
512            2.365_249_2,
513            1.743_016_8,
514            1.165_635_7,
515            -2.191_822_8,
516            2.505_914_4,
517            0.498_677,
518            2.496_595,
519            -0.108_386_315,
520            0.991_436_9,
521            -2.835_525_5,
522        ];
523
524        let angles = ANGLES.map(Angle32::from_radians);
525
526        let sum: Angle32 = angles.iter().copied().sum();
527        let add = angles.iter().copied().fold(Angle::ZERO, |a, b| a + b);
528
529        assert_float_eq!(sum.to_radians(), add.to_radians(), abs <= 1e-5);
530    }
531
532    #[test]
533    fn angle_from_nan_is_nan() {
534        macro_rules! test {
535            (
536                $($nan:expr),*
537            ) => {
538                $(
539                    assert!(Angle::from_radians($nan).is_nan());
540                    assert!(Angle::from_degrees($nan).is_nan());
541                    assert!(Angle::from_turns($nan).is_nan());
542                    assert!(Angle::from_gradians($nan).is_nan());
543                )*
544            };
545        }
546        test!(f32::NAN, f64::NAN);
547    }
548
549    #[test]
550    fn angle_from_infinity_is_nan() {
551        macro_rules! test {
552            (
553                $($inf:expr),*
554            ) => {
555                $(
556                    assert!(Angle::from_radians($inf).is_nan());
557                    assert!(Angle::from_degrees($inf).is_nan());
558                    assert!(Angle::from_turns($inf).is_nan());
559                    assert!(Angle::from_gradians($inf).is_nan());
560                )*
561            };
562        }
563        test!(
564            f32::INFINITY,
565            f32::NEG_INFINITY,
566            f64::INFINITY,
567            f64::NEG_INFINITY
568        );
569    }
570
571    #[test]
572    fn angle_from_big_value_is_not_nan() {
573        macro_rules! test {
574            (
575                $($big_value:expr),*
576            ) => {
577                $(
578                    assert!(!Angle::from_radians($big_value).is_nan());
579                    assert!(!Angle::from_degrees($big_value).is_nan());
580                    assert!(!Angle::from_turns($big_value).is_nan());
581                    assert!(!Angle::from_gradians($big_value).is_nan());
582                )*
583            };
584        }
585        test!(f32::MAX, f32::MIN, f64::MAX, f64::MIN);
586    }
587}