ang/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3use approx::{AbsDiffEq, RelativeEq, UlpsEq};
4use core::cmp::Ordering;
5use core::f64::consts::PI;
6use core::fmt::{Display, Error, Formatter};
7use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
8use num_traits::{
9    cast::{cast, NumCast},
10    Num, Signed, Zero,
11};
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15#[cfg(feature = "std")]
16use num_traits::Float;
17
18/// An angle.
19///
20/// Might be a value in degrees or in radians.
21#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
22#[derive(Copy, Clone, Debug, Hash)]
23pub enum Angle<T = f64> {
24    /// The angle value in radians.
25    Radians(T),
26    /// The angle value in degrees.
27    Degrees(T),
28}
29
30impl<T: Default> Default for Angle<T> {
31    #[inline]
32    fn default() -> Self {
33        Self::Degrees(Default::default())
34    }
35}
36
37impl<T: Copy + NumCast> Angle<T> {
38    /// Yield the value encoded in radians.
39    #[inline]
40    pub fn in_radians(self) -> T {
41        match self {
42            Radians(v) => v,
43            Degrees(v) => cast(cast::<T, f64>(v).unwrap() / 180.0 * PI).unwrap(),
44        }
45    }
46
47    /// Yield the value encoded in degrees.
48    #[inline]
49    pub fn in_degrees(self) -> T {
50        match self {
51            Radians(v) => cast(cast::<T, f64>(v).unwrap() / PI * 180.0).unwrap(),
52            Degrees(v) => v,
53        }
54    }
55
56    /// An angle of 45°.
57    #[inline]
58    pub fn eighth() -> Angle<T> {
59        Degrees(cast(45).unwrap())
60    }
61
62    /// An angle of 90° (right angle).
63    #[inline]
64    pub fn quarter() -> Angle<T> {
65        Degrees(cast(90).unwrap())
66    }
67
68    /// An angle of 180° (straight).
69    #[inline]
70    pub fn half() -> Angle<T> {
71        Degrees(cast(180).unwrap())
72    }
73
74    /// An angle of 360° (perigon).
75    #[inline]
76    pub fn full() -> Angle<T> {
77        Degrees(cast(360).unwrap())
78    }
79}
80
81impl<T: Copy + Num + NumCast + PartialOrd> Angle<T> {
82    /// Create a new angle by normalizing the value into the range of
83    /// [0, 2π) rad.
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// # use ang::*;
89    /// # use std::f64::consts::PI;
90    /// let alpha = Degrees(-90.0f64).normalized();
91    /// assert!((alpha.in_degrees() - 270.0).abs() < 1.0e-10);
92    ///
93    /// let beta = Radians(2.0 * PI).normalized();
94    /// assert!((beta.in_radians() - 0.0).abs() < 1.0e-10);
95    /// ```
96    #[inline]
97    pub fn normalized(self) -> Self {
98        let (v, upper) = match self {
99            Radians(v) => (v, cast(2.0 * PI).unwrap()),
100            Degrees(v) => (v, cast(360.0).unwrap()),
101        };
102
103        let normalized = if v < upper && v >= Zero::zero() {
104            v
105        } else {
106            let v = v % upper;
107
108            if v >= Zero::zero() {
109                v
110            } else {
111                v + upper
112            }
113        };
114
115        match self {
116            Radians(_) => Radians(normalized),
117            Degrees(_) => Degrees(normalized),
118        }
119    }
120}
121
122#[cfg(feature = "std")]
123impl<T: Float> Angle<T> {
124    /// Computes the minimal unsigned distance between two normalized angles. Returns an
125    /// angle in the range of [0, π] rad.
126    ///
127    /// ```rust
128    /// # use ang::*;
129    /// let distance = Degrees(345.0).min_dist(Degrees(15.0));
130    /// assert!((distance.in_degrees() - 30.0) < 1.0e-10);
131    /// ```
132    #[inline]
133    pub fn min_dist(self, other: Angle<T>) -> Angle<T> {
134        let pi = cast(PI).unwrap();
135        let two_pi = cast(2.0 * PI).unwrap();
136
137        let a = self.in_radians();
138        let b = other.in_radians();
139
140        let d = (a - b).abs();
141
142        // short-circuit if both angles are normalized
143        Radians(
144            if a >= T::zero() && a < two_pi && b >= T::zero() && b < two_pi {
145                d.min(two_pi - d)
146            } else {
147                pi - ((d % two_pi) - pi).abs()
148            },
149        )
150    }
151}
152
153impl<T: Signed> Angle<T> {
154    /// Compute the absolute angle.
155    #[inline]
156    pub fn abs(&self) -> Self {
157        match *self {
158            Radians(ref v) => Radians(v.abs()),
159            Degrees(ref v) => Degrees(v.abs()),
160        }
161    }
162
163    /// Returns a number that represents the sign of self.
164    ///
165    /// * `1.0` if the number is positive, `+0.0` or `Float::infinity()`
166    /// * `-1.0` if the number is negative, `-0.0` or `Float::neg_infinity()`
167    /// * `Float::nan()` if the number is `Float::nan()`
168    #[inline]
169    pub fn signum(&self) -> Self {
170        match *self {
171            Radians(ref v) => Radians(v.signum()),
172            Degrees(ref v) => Degrees(v.signum()),
173        }
174    }
175
176    /// Returns `true` if the number is positive and `false` if the number is zero or negative
177    #[inline]
178    pub fn is_positive(&self) -> bool {
179        match *self {
180            Radians(ref v) => v.is_positive(),
181            Degrees(ref v) => v.is_positive(),
182        }
183    }
184
185    /// Returns `true` if the number is negative and `false` if the number is zero or positive.
186    #[inline]
187    pub fn is_negative(&self) -> bool {
188        match *self {
189            Radians(ref v) => v.is_negative(),
190            Degrees(ref v) => v.is_negative(),
191        }
192    }
193}
194
195#[cfg(feature = "std")]
196impl<T: Float + NumCast> Angle<T> {
197    /// Compute the sine of the angle.
198    #[inline]
199    pub fn sin(self) -> T {
200        self.in_radians().sin()
201    }
202
203    /// Compute the cosine of the angle.
204    #[inline]
205    pub fn cos(self) -> T {
206        self.in_radians().cos()
207    }
208
209    /// Compute the tangent of the angle.
210    #[inline]
211    pub fn tan(self) -> T {
212        self.in_radians().tan()
213    }
214
215    /// Simultaneously compute the sine and cosine of the number, `x`.
216    ///
217    /// Return `(sin(x), cos(x))`.
218    #[inline]
219    pub fn sin_cos(self) -> (T, T) {
220        self.in_radians().sin_cos()
221    }
222}
223
224impl<T: Zero + Copy + NumCast> Zero for Angle<T> {
225    #[inline]
226    fn zero() -> Self {
227        Radians(T::zero())
228    }
229
230    #[inline]
231    fn is_zero(&self) -> bool {
232        match self {
233            &Radians(ref v) => v.is_zero(),
234            &Degrees(ref v) => v.is_zero(),
235        }
236    }
237}
238
239impl<T: Copy + NumCast + PartialEq> PartialEq for Angle<T> {
240    #[inline]
241    fn eq(&self, other: &Angle<T>) -> bool {
242        if let (Degrees(ref a), Degrees(ref b)) = (self, other) {
243            a.eq(b)
244        } else {
245            self.in_radians().eq(&other.in_radians())
246        }
247    }
248}
249
250impl<T: Copy + Eq + NumCast> Eq for Angle<T> {}
251
252impl<T: AbsDiffEq + Copy + NumCast> AbsDiffEq for Angle<T> {
253    type Epsilon = T::Epsilon;
254
255    #[inline]
256    fn default_epsilon() -> Self::Epsilon {
257        T::default_epsilon()
258    }
259
260    #[inline]
261    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
262        match (*self, *other) {
263            (Radians(ref v0), Radians(ref v1)) => v0.abs_diff_eq(&v1, epsilon),
264            (_, _) => self.in_degrees().abs_diff_eq(&other.in_degrees(), epsilon),
265        }
266    }
267}
268
269impl<T: RelativeEq + Copy + NumCast> RelativeEq for Angle<T> {
270    #[inline]
271    fn default_max_relative() -> Self::Epsilon {
272        T::default_max_relative()
273    }
274
275    #[inline]
276    fn relative_eq(
277        &self,
278        other: &Self,
279        epsilon: Self::Epsilon,
280        max_relative: Self::Epsilon,
281    ) -> bool {
282        match (*self, *other) {
283            (Radians(ref v0), Radians(ref v1)) => v0.relative_eq(&v1, epsilon, max_relative),
284            (_, _) => self
285                .in_degrees()
286                .relative_eq(&other.in_degrees(), epsilon, max_relative),
287        }
288    }
289}
290
291impl<T: UlpsEq + Copy + NumCast> UlpsEq for Angle<T> {
292    #[inline]
293    fn default_max_ulps() -> u32 {
294        T::default_max_ulps()
295    }
296
297    #[inline]
298    fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
299        match (*self, *other) {
300            (Radians(ref v0), Radians(ref v1)) => v0.ulps_eq(&v1, epsilon, max_ulps),
301            (_, _) => self
302                .in_degrees()
303                .ulps_eq(&other.in_degrees(), epsilon, max_ulps),
304        }
305    }
306}
307
308macro_rules! math_additive(
309    ($bound:ident, $func:ident, $assign_bound:ident, $assign_func:ident) => (
310        impl<T: $bound + Copy + NumCast> $bound for Angle<T> {
311            type Output = Angle<T::Output>;
312            #[inline]
313            fn $func(self, rhs: Angle<T>) -> Self::Output {
314                if let (Degrees(a), Degrees(b)) = (self, rhs) {
315                    Degrees(a.$func(b))
316                } else {
317                    Radians(self.in_radians().$func(rhs.in_radians()))
318                }
319            }
320        }
321
322        impl<T: $assign_bound + Copy + NumCast  > $assign_bound for Angle<T> {
323            #[inline]
324            fn $assign_func(&mut self, rhs: Angle<T>) {
325                if let (Degrees(ref mut a), Degrees(b)) = (*self, rhs)  {
326                    a.$assign_func(b);
327                    *self = Degrees(*a);
328                } else {
329                    let mut val = self.in_radians();
330                    val.$assign_func(rhs.in_radians());
331                    *self = Radians(val);
332                }
333            }
334        }
335    );
336);
337
338math_additive!(Add, add, AddAssign, add_assign);
339math_additive!(Sub, sub, SubAssign, sub_assign);
340
341macro_rules! math_multiplicative(
342    ($bound:ident, $func:ident, $assign_bound:ident, $assign_func:ident, $($t:ident),*) => (
343        impl<T: $bound + Copy> $bound<T> for Angle<T> {
344            type Output = Angle<T::Output>;
345            #[inline]
346            fn $func(self, rhs: T) -> Self::Output {
347                match self {
348                    Radians(v) => Radians(v.$func(rhs)),
349                    Degrees(v) => Degrees(v.$func(rhs))
350                }
351            }
352        }
353
354        impl<T: $assign_bound> $assign_bound<T> for Angle<T> {
355            #[inline]
356            fn $assign_func(&mut self, rhs: T) {
357                match *self {
358                    Radians(ref mut v) => { v.$assign_func(rhs) }
359                    Degrees(ref mut v) => { v.$assign_func(rhs) }
360                }
361            }
362        }
363
364        $(
365            impl $bound<Angle<$t>> for $t {
366                type Output = Angle<$t>;
367                #[inline]
368                fn $func(self, rhs: Angle<$t>) -> Self::Output {
369                    match rhs {
370                        Radians(v) => Radians(self.$func(v)),
371                        Degrees(v) => Degrees(self.$func(v))
372                    }
373                }
374            }
375        )*
376    );
377);
378
379math_multiplicative!(
380    Mul, mul, MulAssign, mul_assign, u8, u16, u32, u64, i8, i16, i32, i64, usize, isize, f32, f64
381);
382math_multiplicative!(
383    Div, div, DivAssign, div_assign, u8, u16, u32, u64, i8, i16, i32, i64, usize, isize, f32, f64
384);
385
386impl<T: Neg> Neg for Angle<T> {
387    type Output = Angle<T::Output>;
388    #[inline]
389    fn neg(self) -> Self::Output {
390        match self {
391            Radians(v) => Radians(-v),
392            Degrees(v) => Degrees(-v),
393        }
394    }
395}
396
397impl<T: PartialOrd + Copy + NumCast> PartialOrd<Angle<T>> for Angle<T> {
398    #[inline]
399    fn partial_cmp(&self, other: &Angle<T>) -> Option<Ordering> {
400        match (*self, *other) {
401            (Radians(ref v0), Radians(ref v1)) => v0.partial_cmp(&v1),
402            (_, _) => self.in_degrees().partial_cmp(&other.in_degrees()),
403        }
404    }
405}
406
407impl<T: Ord + Eq + Copy + NumCast> Ord for Angle<T> {
408    #[inline]
409    fn cmp(&self, other: &Self) -> Ordering {
410        match (*self, *other) {
411            (Radians(ref v0), Radians(ref v1)) => v0.cmp(&v1),
412            (_, _) => self.in_degrees().cmp(&other.in_degrees()),
413        }
414    }
415}
416
417impl<T: Display> Display for Angle<T> {
418    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
419        match *self {
420            Radians(ref v) => write!(f, "{}rad", v),
421            Degrees(ref v) => write!(f, "{}°", v),
422        }
423    }
424}
425
426unsafe impl<T: Send> Send for Angle<T> {}
427
428/// Compute the arcsine of a number. Return value is in the range of
429/// [-π/2, π/2] rad or `None` if the number is outside the range [-1, 1].
430#[cfg(feature = "std")]
431#[inline]
432pub fn asin<T: Float>(value: T) -> Option<Angle<T>> {
433    let value = value.asin();
434    if value.is_nan() {
435        None
436    } else {
437        Some(Radians(value))
438    }
439}
440
441/// Compute the arccosine of a number. Return value is in the range of
442/// [0, π] rad or `None` if the number is outside the range [-1, 1].
443#[cfg(feature = "std")]
444#[inline]
445pub fn acos<T: Float>(value: T) -> Option<Angle<T>> {
446    let value = value.acos();
447    if value.is_nan() {
448        None
449    } else {
450        Some(Radians(value))
451    }
452}
453
454/// Compute the arctangent of a number. Return value is in the range of
455/// [-π/2, π/2] rad.
456#[cfg(feature = "std")]
457#[inline]
458pub fn atan<T: Float>(value: T) -> Angle<T> {
459    Radians(value.atan())
460}
461
462/// Compute the four quadrant arctangent of `y` and `x`.
463#[cfg(feature = "std")]
464#[inline]
465pub fn atan2<T: Float>(y: T, x: T) -> Angle<T> {
466    Radians(y.atan2(x))
467}
468
469/// Compute the approximate mean of a list of angles by averaging the
470/// Cartesian coordinates of the angles on the unit circle. Return the
471/// normalized angle.
472///
473/// # Examples
474///
475/// ```rust
476/// # use ang::*;
477/// let angles = [Degrees(270.0f64), Degrees(360.0), Degrees(90.0)];
478///
479/// let mu = mean_angle(&angles);
480/// assert!(mu.min_dist(Radians(0.0)).in_radians() < 1.0e-10);
481/// ```
482#[cfg(feature = "std")]
483#[inline]
484pub fn mean_angle<'a, T, I>(angles: I) -> Angle<T>
485where
486    T: 'a + Float,
487    I: IntoIterator<Item = &'a Angle<T>>,
488{
489    let mut x = T::zero();
490    let mut y = T::zero();
491    let mut n = 0;
492
493    for angle in angles {
494        let (sin, cos) = angle.sin_cos();
495
496        x = x + cos;
497        y = y + sin;
498        n += 1;
499    }
500
501    let n = cast(n).unwrap();
502    let a = (y / n).atan2(x / n);
503
504    Radians(a).normalized()
505}
506
507// re-exports
508pub use Angle::{Degrees, Radians};
509
510#[cfg(test)]
511#[allow(deprecated)]
512mod tests {
513    use core::f64::consts::PI;
514    use hamcrest2::{assert_that, close_to, prelude::*};
515    use num_traits::cast::cast;
516    use quickcheck::{quickcheck, Arbitrary, Gen};
517
518    #[cfg(feature = "std")]
519    use num_traits::Float;
520
521    use super::*;
522
523    #[test]
524    fn test_angle_conversions() {
525        fn prop(angle: Angle) -> bool {
526            are_close(angle.in_radians(), Degrees(angle.in_degrees()).in_radians())
527        }
528        quickcheck(prop as fn(Angle) -> bool);
529    }
530
531    #[test]
532    fn test_angle_math_multiplicative() {
533        fn prop(a: Angle, x: f64) -> bool {
534            match a {
535                Radians(v) => {
536                    let div_res = {
537                        let mut a1 = a.clone();
538                        a1 /= x;
539                        a1.in_radians() == v / x
540                    };
541                    let mult_res = {
542                        let mut a1 = a.clone();
543                        a1 *= x;
544                        a1.in_radians() == v * x
545                    };
546                    (a * x).in_radians() == v * x
547                        && (a / x).in_radians() == v / x
548                        && div_res
549                        && mult_res
550                }
551                Degrees(v) => {
552                    let div_res = {
553                        let mut a1 = a.clone();
554                        a1 *= x;
555                        a1.in_degrees() == v * x
556                    };
557                    let mult_res = {
558                        let mut a1 = a.clone();
559                        a1 /= x;
560                        a1.in_degrees() == v / x
561                    };
562                    (a * x).in_degrees() == v * x
563                        && (a / x).in_degrees() == v / x
564                        && div_res
565                        && mult_res
566                }
567            }
568        }
569        quickcheck(prop as fn(Angle, f64) -> bool);
570    }
571
572    #[test]
573    fn test_angle_math_additive() {
574        fn prop(a: Angle, b: Angle) -> bool {
575            if let (Radians(x), Radians(y)) = (a, b) {
576                let add_res = {
577                    let mut a1 = a.clone();
578                    a1 += b;
579                    a1.in_radians() == x + y
580                };
581                let sub_res = {
582                    let mut a1 = a.clone();
583                    a1 -= b;
584                    a1.in_radians() == x - y
585                };
586                (a + b).in_radians() == x + y && (a - b).in_radians() == x - y && add_res && sub_res
587            } else if let (Degrees(x), Degrees(y)) = (a, b) {
588                let add_res = {
589                    let mut a1 = a.clone();
590                    a1 += b;
591                    a1.in_degrees() == x + y
592                };
593                let sub_res = {
594                    let mut a1 = a.clone();
595                    a1 -= b;
596                    a1.in_degrees() == x - y
597                };
598                (a + b).in_degrees() == x + y && (a - b).in_degrees() == x - y && add_res && sub_res
599            } else {
600                let add_res = {
601                    let mut a1 = a.clone();
602                    a1 += b;
603                    a1.in_radians() == a.in_radians() + b.in_radians()
604                };
605                let sub_res = {
606                    let mut a1 = a.clone();
607                    a1 -= b;
608                    a1.in_radians() == a.in_radians() - b.in_radians()
609                };
610                (a + b).in_radians() == a.in_radians() + b.in_radians() && add_res && sub_res
611            }
612        }
613        quickcheck(prop as fn(Angle, Angle) -> bool);
614    }
615
616    #[test]
617    fn test_angle_normalization() {
618        fn prop(angle: Angle) -> bool {
619            let v = angle.normalized();
620            let rad = v.in_radians();
621            let deg = v.in_degrees();
622
623            0.0 <= rad
624                && rad < 2.0 * PI
625                && 0.0 <= deg
626                && deg < 360.0
627                && are_close(rad.cos(), angle.cos())
628        }
629        quickcheck(prop as fn(Angle) -> bool);
630    }
631
632    #[test]
633    fn test_angle_minimal_distance() {
634        fn prop(a: Angle, b: Angle) -> bool {
635            let d = a.min_dist(b);
636            0.0 <= d.in_radians() && d.in_radians() <= PI
637        }
638        quickcheck(prop as fn(Angle, Angle) -> bool);
639
640        assert_that!(
641            Degrees(180.0).min_dist(Degrees(0.0)).in_degrees(),
642            close_to(180.0, 0.000001)
643        );
644        assert_that!(
645            Degrees(0.1).min_dist(Degrees(359.9)).in_degrees(),
646            close_to(0.2, 0.000001)
647        );
648        assert_that!(
649            Degrees(1.0).min_dist(Degrees(2.0)).in_degrees(),
650            close_to(1.0, 0.000001)
651        );
652    }
653
654    #[test]
655    pub fn test_mean_angle() {
656        assert_that!(
657            mean_angle(&[Degrees(90.0)]).in_degrees(),
658            close_to(90.0, 0.000001)
659        );
660        assert_that!(
661            mean_angle(&[Degrees(90.0), Degrees(90.0)]).in_degrees(),
662            close_to(90.0, 0.000001)
663        );
664        assert_that!(
665            mean_angle(&[Degrees(90.0), Degrees(180.0), Degrees(270.0)]).in_degrees(),
666            close_to(180.0, 0.000001)
667        );
668        assert_that!(
669            mean_angle(&[Degrees(20.0), Degrees(350.0)]).in_degrees(),
670            close_to(5.0, 0.000001)
671        );
672    }
673
674    #[cfg(feature = "std")]
675    fn are_close<T: Float>(a: T, b: T) -> bool {
676        (a - b).abs() < cast(1.0e-10).unwrap()
677    }
678
679    impl<T: Arbitrary> Arbitrary for Angle<T> {
680        fn arbitrary<G: Gen>(g: &mut G) -> Self {
681            let v = Arbitrary::arbitrary(g);
682            if bool::arbitrary(g) {
683                Radians(v)
684            } else {
685                Degrees(v)
686            }
687        }
688    }
689}