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#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
22#[derive(Copy, Clone, Debug, Hash)]
23pub enum Angle<T = f64> {
24 Radians(T),
26 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 #[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 #[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 #[inline]
58 pub fn eighth() -> Angle<T> {
59 Degrees(cast(45).unwrap())
60 }
61
62 #[inline]
64 pub fn quarter() -> Angle<T> {
65 Degrees(cast(90).unwrap())
66 }
67
68 #[inline]
70 pub fn half() -> Angle<T> {
71 Degrees(cast(180).unwrap())
72 }
73
74 #[inline]
76 pub fn full() -> Angle<T> {
77 Degrees(cast(360).unwrap())
78 }
79}
80
81impl<T: Copy + Num + NumCast + PartialOrd> Angle<T> {
82 #[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 #[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 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 #[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 #[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 #[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 #[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 #[inline]
199 pub fn sin(self) -> T {
200 self.in_radians().sin()
201 }
202
203 #[inline]
205 pub fn cos(self) -> T {
206 self.in_radians().cos()
207 }
208
209 #[inline]
211 pub fn tan(self) -> T {
212 self.in_radians().tan()
213 }
214
215 #[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#[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#[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#[cfg(feature = "std")]
457#[inline]
458pub fn atan<T: Float>(value: T) -> Angle<T> {
459 Radians(value.atan())
460}
461
462#[cfg(feature = "std")]
464#[inline]
465pub fn atan2<T: Float>(y: T, x: T) -> Angle<T> {
466 Radians(y.atan2(x))
467}
468
469#[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
507pub 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}