angular/
lib.rs

1extern crate num;
2
3#[cfg(test)] extern crate hamcrest;
4#[cfg(test)] extern crate quickcheck;
5
6
7use num::{Float, Num, NumCast, Signed, Zero, cast};
8use std::f64::consts::PI;
9use std::fmt::{Display, Formatter, Error};
10use std::ops::{Add, Div, Mul, Neg, Sub};
11
12
13/// An angle.
14///
15/// Might be a value in degrees or in radians.
16#[derive(Copy, Clone, Debug)]
17pub enum Angle<T=f64> {
18    Radians(T),
19    Degrees(T)
20}
21
22impl<T: NumCast> Angle<T> {
23    /// Yield the value encoded in radians.
24    pub fn in_radians(self) -> T {
25        match self {
26            Radians(v) => v,
27            Degrees(v) => cast(cast::<T, f64>(v).unwrap() / 180.0 * PI).unwrap()
28        }
29    }
30
31    /// Yield the value encoded in degrees.
32    pub fn in_degrees(self) -> T {
33        match self {
34            Radians(v) => cast(cast::<T, f64>(v).unwrap() / PI * 180.0).unwrap(),
35            Degrees(v) => v
36        }
37    }
38
39    /// An angle of 45°.
40    pub fn eighth() -> Angle<T> {
41        Degrees(cast(45).unwrap())
42    }
43
44    /// An angle of 90° (right angle).
45    pub fn quarter() -> Angle<T> {
46        Degrees(cast(90).unwrap())
47    }
48
49    /// An angle of 180° (straight).
50    pub fn half() -> Angle<T> {
51        Degrees(cast(180).unwrap())
52    }
53
54    /// An angle of 360° (perigon).
55    pub fn full() -> Angle<T> {
56        Degrees(cast(360).unwrap())
57    }
58}
59
60impl<T: Copy + Num + NumCast + PartialOrd> Angle<T> {
61    /// Create a new angle by normalizing the value into the range of
62    /// [0, 2π) rad.
63    pub fn normalized(self) -> Self {
64        let (v, upper) = match self {
65            Radians(v) => (v, cast(2.0 * PI).unwrap()),
66            Degrees(v) => (v, cast(360.0).unwrap())
67        };
68
69        let normalized = if v < upper && v >= Zero::zero() {
70            v
71        } else {
72            let v = v % upper;
73
74            if v >= Zero::zero() {
75                v
76            } else {
77                v + upper
78            }
79        };
80
81        match self {
82            Radians(_) => Radians(normalized),
83            Degrees(_) => Degrees(normalized)
84        }
85    }
86}
87
88impl<T: Float> Angle<T> {
89    /// Computes the minimal unsigned distance between two normalized angles. Returns an
90    /// angle in the range of [0, π] rad.
91    ///
92    /// ```rust
93    /// # use angular::*;
94    /// let distance = Degrees(345.0).min_dist(Degrees(15.0));
95    /// assert!((distance.in_degrees() - 30.0) < 1.0e-10);
96    /// ```
97    pub fn min_dist(self, other: Angle<T>) -> Angle<T> {
98        let pi = cast(PI).unwrap();
99        let two_pi = cast(2.0 * PI).unwrap();
100
101        let a = self.in_radians();
102        let b = other.in_radians();
103
104        let d = (a - b).abs();
105
106        // short-circout if both angles are normalized
107        Radians(if a >= T::zero() && a < two_pi && b >= T::zero() && b < two_pi {
108            d.min(two_pi - d)
109        } else {
110            pi - ((d % two_pi) - pi).abs()
111        })
112    }
113}
114
115impl<T: Signed> Angle<T> {
116    /// Compute the absolute angle.
117    pub fn abs(self) -> Self {
118        match self {
119            Radians(v) => Radians(v.abs()),
120            Degrees(v) => Degrees(v.abs())
121        }
122    }
123}
124
125impl<T: Float + NumCast> Angle<T> {
126    /// Compute the sine of the angle.
127    pub fn sin(self) -> T {
128        self.in_radians().sin()
129    }
130
131    /// Compute the cosine of the angle.
132    pub fn cos(self) -> T {
133        self.in_radians().cos()
134    }
135
136    /// Compute the tangent of the angle.
137    pub fn tan(self) -> T {
138        self.in_radians().tan()
139    }
140
141    /// Simultaneously compute the sine and cosine of the number, `x`.
142    /// Return `(sin(x), cos(x))`.
143    pub fn sin_cos(self) -> (T, T) {
144        self.in_radians().sin_cos()
145    }
146}
147
148impl<T: Zero + Copy + NumCast> Zero for Angle<T> {
149    fn zero() -> Self {
150        Radians(T::zero())
151    }
152
153    fn is_zero(&self) -> bool {
154        match self {
155            &Radians(ref v) => v.is_zero(),
156            &Degrees(ref v) => v.is_zero()
157        }
158    }
159}
160
161impl<T: PartialEq + Copy + NumCast> PartialEq for Angle<T> {
162    fn eq(&self, other: &Angle<T>) -> bool {
163        if let (Degrees(a), Degrees(b)) = (*self, *other) {
164            a == b
165        } else {
166            self.in_radians() == other.in_radians()
167        }
168    }
169}
170
171macro_rules! math_additive(
172    ($bound:ident, $func:ident) => (
173        impl<T: $bound + Copy + NumCast> $bound for Angle<T> {
174            type Output = Angle<T::Output>;
175            fn $func(self, rhs: Angle<T>) -> Self::Output {
176                if let (Degrees(a), Degrees(b)) = (self, rhs) {
177                    Degrees(a.$func(b))
178                } else {
179                    Radians(self.in_radians().$func(rhs.in_radians()))
180                }
181            }
182        }
183    );
184);
185
186math_additive!(Add, add);
187math_additive!(Sub, sub);
188
189macro_rules! math_multiplicative(
190    ($bound:ident, $func:ident, $($t:ident),*) => (
191        impl<T: $bound> $bound<T> for Angle<T> {
192            type Output = Angle<T::Output>;
193            fn $func(self, rhs: T) -> Self::Output {
194                match self {
195                    Radians(v) => Radians(v.$func(rhs)),
196                    Degrees(v) => Degrees(v.$func(rhs))
197                }
198            }
199        }
200
201        $(
202            impl $bound<Angle<$t>> for $t {
203                type Output = Angle<$t>;
204                fn $func(self, rhs: Angle<$t>) -> Self::Output {
205                    match rhs {
206                        Radians(v) => Radians(self.$func(v)),
207                        Degrees(v) => Degrees(self.$func(v))
208                    }
209                }
210            }
211        )*
212    );
213);
214
215math_multiplicative!(Mul, mul, u8, u16, u32, u64, i8, i16, i32, i64, usize, isize, f32, f64);
216math_multiplicative!(Div, div, u8, u16, u32, u64, i8, i16, i32, i64, usize, isize, f32, f64);
217
218impl<T: Neg> Neg for Angle<T> {
219    type Output = Angle<T::Output>;
220    fn neg(self) -> Self::Output {
221        match self {
222            Radians(v) => Radians(-v),
223            Degrees(v) => Degrees(-v)
224        }
225    }
226}
227
228impl<T: Display> Display for Angle<T> {
229    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
230        match *self {
231            Radians(ref v) => write!(f, "{}rad", v),
232            Degrees(ref v) => write!(f, "{}°", v)
233        }
234    }
235}
236
237unsafe impl<T: Send> Send for Angle<T> {  }
238
239
240/// Compute the arcsine of a number. Return value is in the range of
241/// [-π/2, π/2] rad or `None` if the number is outside the range [-1, 1].
242pub fn asin<T: Float>(value: T) -> Option<Angle<T>> {
243    let value = value.asin();
244    if value.is_nan() {
245        None
246    } else {
247        Some(Radians(value))
248    }
249}
250
251/// Compute the arccosine of a number. Return value is in the range of
252/// [0, π] rad or `None` if the number is outside the range [-1, 1].
253pub fn acos<T: Float>(value: T) -> Option<Angle<T>> {
254    let value = value.acos();
255    if value.is_nan() {
256        None
257    } else {
258        Some(Radians(value))
259    }
260}
261
262/// Compute the arctangent of a number. Return value is in the range of
263/// [-π/2, π/2] rad.
264pub fn atan<T: Float>(value: T) -> Angle<T> {
265    Radians(value.atan())
266}
267
268/// Compute the four quadrant arctangent of `y` and `x`.
269pub fn atan2<T: Float>(y: T, x: T) -> Angle<T> {
270    Radians(y.atan2(x))
271}
272
273/// Compute the approximate mean of a list of angles by averaging the
274/// Cartesian coordinates of the angles on the unit circle. Return the
275/// normalized angle.
276pub fn mean_angle<T: Float>(angles: &[Angle<T>]) -> Angle<T>
277{
278    let mut x = T::zero();
279    let mut y = T::zero();
280
281    for angle in angles {
282        let (sin, cos) = angle.sin_cos();
283
284        x = x + cos;
285        y = y + sin;
286    }
287
288    let n = cast(angles.len()).unwrap();
289    let a = (y/n).atan2(x/n);
290
291    Radians(a).normalized()
292}
293
294
295// re-exports
296pub use Angle::{Radians, Degrees};
297
298
299#[cfg(test)]
300mod tests {
301    use hamcrest::{assert_that, is, close_to};
302    use num::{Float, cast};
303    use quickcheck::{Arbitrary, Gen, quickcheck};
304    use std::f64::consts::PI;
305
306    use super::*;
307
308    #[test]
309    fn test_angle_conversions() {
310        fn prop(angle: Angle) -> bool {
311            are_close(angle.in_radians(), Degrees(angle.in_degrees()).in_radians())
312        }
313        quickcheck(prop as fn(Angle) -> bool);
314    }
315
316    #[test]
317    fn test_angle_math_multiplicative() {
318        fn prop(a: Angle, x: f64) -> bool {
319            match a {
320                Radians(v) => (a * x).in_radians() == v * x &&
321                              (a / x).in_radians() == v / x,
322                Degrees(v) => (a * x).in_degrees() == v * x &&
323                              (a / x).in_degrees() == v / x
324            }
325        }
326        quickcheck(prop as fn(Angle, f64) -> bool);
327    }
328
329    #[test]
330    fn test_angle_math_additive() {
331        fn prop(a: Angle, b: Angle) -> bool {
332            if let (Radians(x), Radians(y)) = (a, b) {
333                (a + b).in_radians() == x + y &&
334                (a - b).in_radians() == x - y
335            } else if let (Degrees(x), Degrees(y)) = (a, b) {
336                (a + b).in_degrees() == x + y &&
337                (a - b).in_degrees() == x - y
338            } else {
339                (a + b).in_radians() == a.in_radians() + b.in_radians()
340            }
341        }
342        quickcheck(prop as fn(Angle, Angle) -> bool);
343    }
344
345    #[test]
346    fn test_angle_normalization() {
347        fn prop(angle: Angle) -> bool {
348            let v = angle.normalized();
349            let rad = v.in_radians();
350            let deg = v.in_degrees();
351
352            0.0 <= rad && rad < 2.0 * PI &&
353            0.0 <= deg && deg < 360.0 &&
354            are_close(rad.cos(), angle.cos())
355        }
356        quickcheck(prop as fn(Angle) -> bool);
357    }
358
359    #[test]
360    fn test_angle_minimal_distance() {
361        fn prop(a: Angle, b: Angle) -> bool {
362            let d = a.min_dist(b);
363            0.0 <= d.in_radians() && d.in_radians() <= PI
364        }
365        quickcheck(prop as fn(Angle, Angle) -> bool);
366
367        assert_that(Degrees(180.0).min_dist(Degrees(0.0)).in_degrees(), is(close_to(180.0, 0.000001)));
368        assert_that(Degrees(0.1).min_dist(Degrees(359.9)).in_degrees(), is(close_to(0.2, 0.000001)));
369        assert_that(Degrees(1.0).min_dist(Degrees(2.0)).in_degrees(), is(close_to(1.0, 0.000001)));
370    }
371
372    #[test]
373    pub fn test_mean_angle() {
374        assert_that(mean_angle(&[Degrees(90.0)]).in_degrees(), is(close_to(90.0, 0.000001)));
375        assert_that(mean_angle(&[Degrees(90.0), Degrees(90.0)]).in_degrees(), is(close_to(90.0, 0.000001)));
376        assert_that(mean_angle(&[Degrees(90.0), Degrees(180.0), Degrees(270.0)]).in_degrees(), is(close_to(180.0, 0.000001)));
377        assert_that(mean_angle(&[Degrees(20.0), Degrees(350.0)]).in_degrees(), is(close_to(5.0, 0.000001)));
378    }
379
380    fn are_close<T: Float>(a: T, b: T) -> bool {
381        (a - b).abs() < cast(1.0e-10).unwrap()
382    }
383
384    impl<T: Arbitrary> Arbitrary for Angle<T> {
385        fn arbitrary<G: Gen>(g: &mut G) -> Self {
386            let v = Arbitrary::arbitrary(g);
387            if bool::arbitrary(g) {
388                Radians(v)
389            } else {
390                Degrees(v)
391            }
392        }
393    }
394}