devela/num/geom/metric/angle/impl/
float.rs

1// devela::num::geom::metric::angle::impl::float
2//
3//!
4//
5
6#[cfg(_float··)]
7#[allow(unused_imports)]
8use crate::{Angle, AngleDirection, AngleKind, ExtFloat, ExtFloatConst, Float};
9
10/// impl `Angle` methods with a floating-point representation.
11///
12/// # Macro arguments
13/// $f: the inner floating-point type
14/// $cap: the capability that enables the implementation. E.g "_float_f32".
15/// $cmp: the capability associated to some methods. E.g. _cmp_f32.
16macro_rules! impl_angle {
17    () => {
18        impl_angle![float
19            f32:"_float_f32":"_cmp_f32",
20            f64:"_float_f64":"_cmp_f64"
21        ];
22    };
23    (float $($f:ty : $cap:literal : $cmp:literal),+) => {
24        $( impl_angle![@float $f:$cap:$cmp]; )+
25    };
26    (@float $f:ty : $cap:literal : $cmp:literal) => {
27        #[doc = concat!("# Methods for angles represented using `", stringify!($f), "`.")]
28        #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = $cap)))]
29        #[cfg(feature = $cap )]
30        impl Angle<$f> {
31            /* construct */
32
33            /// Creates a normalized full positive angle at 0 degrees.
34            pub const fn new_full() -> Self { Self::new(0.0) }
35
36            /// Creates a normalized right positive angle at 90 degrees (0.25).
37            pub const fn new_right() -> Self { Self::new(0.25) }
38
39            /// Creates a normalized straight positive angle at 180 degrees (0.5).
40            pub const fn new_straight() -> Self { Self::new(0.5) }
41
42            /// Creates a new angle from a `radians` value.
43            pub const fn from_rad(radians: $f) -> Self { Self::new(radians / <$f>::TAU) }
44
45            /// Creates a new angle from a `degrees` value.
46            pub const fn from_deg(degrees: $f) -> Self { Self::new(degrees / 360.0) }
47
48            /// Creates a new angle from a `value` in a `custom_unit` which represents a full turn.
49            pub const fn from_custom(value: $f, custom_unit: $f) -> Self {
50                Self::new(value / custom_unit)
51            }
52
53            /* convert */
54
55            /// Converts the angle to radians.
56            #[must_use]
57            pub const fn to_rad(self) -> $f { self.turn * <$f>::TAU }
58
59            /// Converts the angle to degrees.
60            #[must_use]
61            pub const fn to_deg(self) -> $f { self.turn * 360.0 }
62
63            /// Converts the angle to a `custom_unit` which represents a full turn.
64            #[must_use]
65            pub const fn to_custom(self, custom_unit: $f) -> $f { self.turn * custom_unit }
66
67            /* normalize */
68
69            /// Returns `true` if the angle is between -1 and 1 (non-inclusive).
70            #[cfg(feature = $cmp)]
71            #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = $cmp)))]
72            pub const fn is_normalized(self) -> bool {
73                crate::Compare(self.turn).gt(-1.0) && crate::Compare(self.turn).lt(1.0)
74            }
75
76            /// Returns the angle normalized to the non-inclusive range -1 to 1.
77            // BLOCKED: const by fract
78            pub fn normalize(self) -> Self { Self::new(self.turn.fract()) }
79
80            /// Sets the angle normalized to the non-inclusive range -1 to 1.
81            // BLOCKED: const by fract
82            pub fn set_normalized(&mut self) { self.turn = self.turn.fract(); }
83
84            /* direction */
85
86            /// Returns the angle direction.
87            ///
88            /// Since the floating-point representation always maintains the sign
89            /// the direction can't be undefined.
90            pub const fn direction(self) -> AngleDirection {
91                use AngleDirection::{Negative, Positive};
92                if Float(self.turn).is_sign_negative() { Negative } else { Positive }
93            }
94
95            /// Returns `true` if the angle has the given `direction`.
96            ///
97            /// Since the floating-point representation always maintains the sign
98            /// the direction can't be undefined, and it will return `false` in that case.
99            #[must_use]
100            pub const fn has_direction(self, direction: AngleDirection) -> bool {
101                direction as i8 == self.direction() as i8
102            }
103
104            /// Returns a version of the angle with the given `direction`.
105            ///
106            /// An `Undefined` direction will be interpreted as counter-clockwise (positive).
107            pub const fn with_direction(self, direction: AngleDirection) -> Self {
108                use AngleDirection as D;
109                match direction {
110                    D::Positive | D::Undefined => Self::new(Float(self.turn).abs().0),
111                    D::Negative => Self::new(Float(self.turn).neg_abs().0),
112                }
113            }
114
115            /// Sets the angle to the given `direction`.
116            ///
117            /// An `Undefined` direction will be interpreted as counter-clockwise (positive).
118            pub const fn set_direction(&mut self, direction: AngleDirection) {
119                use AngleDirection as D;
120                match direction {
121                    D::Positive | D::Undefined => self.turn = Float(self.turn).abs().0,
122                    D::Negative => self.turn = Float(self.turn).neg_abs().0,
123                }
124            }
125
126            /// Returns a version of the angle with inverted direction.
127            pub const fn invert_direction(self) -> Self {
128                Self::new(Float(self.turn).flip_sign().0)
129            }
130
131            /// Returns the negative version of the angle.
132            pub const fn negative(self) -> Self { Self::new(Float(self.turn).neg_abs().0) }
133
134            /// Sets the angle as negative.
135            pub const fn set_negative(&mut self) { { self.turn = Float(self.turn).neg_abs().0; } }
136
137            /// Returns the positive version of the angle.
138            pub const fn positive(self) -> Self { Self::new(Float(self.turn).abs().0) }
139
140            /// Sets the angle as positive.
141            pub const fn set_positive(&mut self) { self.turn = Float(self.turn).abs().0; }
142
143            /* kind */
144
145            /// Returns the kind of the normalized angle.
146            // BLOCKED: const by normalize
147            #[cfg(feature = $cmp)]
148            #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = $cmp)))]
149            pub fn kind(self) -> AngleKind {
150                let angle = crate::Compare(self.normalize().positive().turn);
151                use AngleKind::{Full, Acute, Right, Obtuse, Straight, Reflex};
152                if angle.eq(0.0) { // 1 turn (0' or 360º)
153                    Full
154                } else if angle.eq(0.25) { // 1/4 turn (90º)
155                    Right
156                } else if angle.eq(0.5) { // 1/2 turn (180º)
157                    Straight
158                } else if angle.lt(0.25) { // < 1/4 turn (< 90º)
159                    Acute
160                } else if angle.lt(0.5) { // < 1/2 turn (< 180º)
161                    Obtuse
162                } else { // < 1 turn (< 360º)
163                    Reflex
164                }
165            }
166            /// Returns the kind of the angle using a custom tolerance for approximate matching.
167            // BLOCKED: const by normalize
168            pub fn kind_approx(self, tolerance: $f) -> AngleKind {
169                let angle = self.normalize().positive().turn;
170                use AngleKind::{Full, Acute, Right, Obtuse, Straight, Reflex};
171                if (angle - 0.0).abs() <= tolerance {
172                    Full
173                } else if (angle - 0.25).abs() <= tolerance {
174                    Right
175                } else if (angle - 0.5).abs() <= tolerance {
176                    Straight
177                } else if angle < 0.25 {
178                    Acute
179                } else if angle < 0.5 {
180                    Obtuse
181                } else {
182                    Reflex
183                }
184            }
185
186            /// Returns `true` if the angle is of the given `kind`.
187            #[must_use]
188            #[cfg(feature = $cmp)]
189            #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = $cmp)))]
190            // BLOCKED: const by normalize
191            pub fn is_kind(self, kind: AngleKind) -> bool {
192                let angle = crate::Compare(self.normalize().positive().turn);
193                use AngleKind::{Full, Acute, Right, Obtuse, Straight, Reflex};
194                match kind {
195                    Full => angle.eq(0.0),
196                    Right => angle.eq(0.25),
197                    Straight => angle.eq(0.5),
198                    Acute => angle.gt(0.0) && angle.lt(0.25),
199                    Obtuse => angle.gt(0.25) && angle.lt(0.5),
200                    Reflex => angle.gt(0.5) && angle.lt(1.0),
201                }
202            }
203
204            /// Returns `true` if the angle is of the given `kind` using a custom tolerance.
205            #[must_use]
206            // BLOCKED: const by normalize
207            pub fn is_kind_approx(self, kind: AngleKind, tolerance: $f) -> bool {
208                let angle = self.normalize().positive().turn;
209                match kind {
210                    AngleKind::Full => (angle - 0.0).abs() <= tolerance,
211                    AngleKind::Right => (angle - 0.25).abs() <= tolerance,
212                    AngleKind::Straight => (angle - 0.5).abs() <= tolerance,
213                    AngleKind::Acute => angle > 0.0 && angle < 0.25,
214                    AngleKind::Obtuse => angle > 0.25 && angle < 0.5,
215                    AngleKind::Reflex => angle > 0.5 && angle < 1.0,
216                }
217            }
218        }
219    };
220}
221impl_angle!();