Skip to main content

hexga_math/geometry/
angle.rs

1/// This module provides a generic 2D angle type `AngleOf<T>` that supports units such as degrees, radians, and turns.
2///
3/// # Features
4/// - Conversion between degrees, radians, and turns.
5/// - Normalization of angles to standard ranges.
6/// - Trigonometric functions (sin, cos, tan, etc.).
7///
8/// ```rust
9/// use hexga_math::prelude::*;
10///
11/// let a = 90.0f32.degree();
12/// println!("Angle in radians: {}", a.radian());
13/// println!("Angle in degrees: {}", a.degree());
14/// ```
15use super::*;
16
17new_unit!(
18    /// 2D Angle, support degree, radian, turn...
19    ///
20    /// Provides conversion to other angle units
21    AngleOf
22);
23
24/// 2D Angle, support degree, radian, turn...
25///
26/// Provides conversion to other angle units
27///
28///
29/// Uses `float` as its underlying representation.
30///
31/// See [`AngleOf`] to use your own precision.
32pub type Angle = AngleOf<float>;
33
34pub trait ToAngle<T>: ToAngleComposite<Output = AngleOf<T>>
35where
36    T: CastIntoFloat,
37{
38}
39impl<S, T> ToAngle<T> for S
40where
41    S: ToAngleComposite<Output = AngleOf<T>>,
42    T: CastIntoFloat,
43{
44}
45
46pub trait ToAngleComposite
47{
48    type Output;
49    fn degree(self) -> Self::Output;
50    fn radian(self) -> Self::Output;
51    fn turn(self) -> Self::Output;
52}
53
54map_on_number!(
55    ($name:ident) =>
56    {
57        impl ToAngleComposite for $name
58        {
59            type Output = Angle;
60            fn degree(self) -> Angle { Angle::from_degree(self.to_float()) }
61            fn radian(self) -> Angle { Angle::from_radian(self.to_float()) }
62            fn turn  (self) -> Angle { Angle::from_turn  (self.to_float()) }
63        }
64    }
65);
66
67impl<T> ToAngleComposite for T
68where
69    T: Map,
70    T::Item: ToAngleComposite,
71{
72    type Output = T::WithType<<T::Item as ToAngleComposite>::Output>;
73    fn degree(self) -> Self::Output { self.map(ToAngleComposite::degree) }
74    fn radian(self) -> Self::Output { self.map(ToAngleComposite::radian) }
75    fn turn(self) -> Self::Output { self.map(ToAngleComposite::turn) }
76}
77
78#[cfg(feature = "serde")]
79impl<T> Serialize for AngleOf<T>
80where
81    T: Float + Serialize,
82{
83    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84    where
85        S: Serializer,
86    {
87        self.degree().serialize(serializer)
88        // if serializer.is_human_readable()
89        // {
90        //     return
91        // }else
92        // {
93
94        // }
95    }
96}
97
98/*
99#[cfg(feature = "serde")]
100#[cfg_attr(feature = "serde", derive(Deserialize), serde(untagged))]
101enum AngleInput<T> where T: Float
102{
103    Degree (T),
104    Prefix { deg: Option<T>, rad: Option<T>, turn: Option<T> },
105    // Postfix (String) // To support "90°", "90deg" "3.14rad" ?
106}
107*/
108
109#[cfg(feature = "serde")]
110impl<'de, T> Deserialize<'de> for AngleOf<T>
111where
112    T: Float + Deserialize<'de>,
113{
114    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
115    where
116        D: Deserializer<'de>,
117    {
118        Ok(Self::from_degree(T::deserialize(deserializer)?))
119
120        /*
121        if !deserializer.is_human_readable()
122        {
123            return Ok(Self::from_degree(T::deserialize(deserializer)?));
124        }
125
126        //deserializer.deserialize_any(visitor)
127
128        match AngleInput::deserialize(deserializer)
129        {
130            Ok(v) => match v
131            {
132                AngleInput::Degree(d) => Ok(Self::from_degree(d)),
133                AngleInput::Prefix { deg, rad, turn } =>
134                {
135                    match deg.is_some() as u8 + rad.is_some() as u8 + turn.is_some() as u8
136                    {
137                        0 => Err(serde::de::Error::custom("Missing `deg`, `rad` or `turn`")),
138                        1 =>
139                        {
140                            if let Some(d) = deg  { return Ok(Self::from_degree(d)) }
141                            if let Some(r) = rad  { return Ok(unsafe { Self::from_inner_value(r) }) }
142                            if let Some(t) = turn { return Ok(Self::from_turn(t)) }
143                            unreachable!()
144                        }
145                        _ => Err(serde::de::Error::custom("Only one of `deg`, `rad` or `turn` is expected")),
146                    }
147                },
148            }
149            Err(_) => Err(serde::de::Error::custom("Expected an Angle in degree, ex `90`. Available unit: `deg`, `rad` or `turn`. ex: `(deg:45)`.")),
150        }
151        */
152    }
153}
154
155impl<T: Float> AngleOf<T>
156{
157    /// `360°`
158    pub const FULL: Self = AngleOf(T::TWO_PI);
159    /// `180°`
160    pub const HALF: Self = AngleOf(T::PI);
161    /// `180°`
162    pub const FLAT: Self = AngleOf(T::PI);
163    /// `90°`
164    pub const RIGHT: Self = AngleOf(T::HALF_PI);
165
166    pub fn from_radian(rad: T) -> Self { Self(rad) }
167    pub fn from_degree(degree: T) -> Self
168    {
169        Self(degree * (T::ANGLE_FULL_RADIAN / T::ANGLE_FULL_DEGREE))
170    }
171    pub fn from_turn(coef: T) -> Self { Self(coef * T::ANGLE_FULL_RADIAN) }
172
173    pub fn radian(self) -> T { self.0 }
174    pub fn degree(self) -> T { self.0 * (T::ANGLE_FULL_DEGREE / T::ANGLE_FULL_RADIAN) }
175    pub fn turn(self) -> T { self.0 / T::ANGLE_FULL_RADIAN }
176
177    // Todo : check for a better way to do it
178    /// `[0, 2PI[`
179    pub fn normalized_positive(self) -> Self
180    {
181        Self::from_radian(
182            (self.0 % T::ANGLE_FULL_RADIAN + T::ANGLE_FULL_RADIAN) % T::ANGLE_FULL_RADIAN,
183        )
184    }
185
186    // Todo : check for a better way to do it
187    /// `]PI; PI]`
188    pub fn normalized(self) -> Self
189    {
190        let tmp = self.normalized_positive();
191        if tmp < Self::HALF
192        {
193            tmp
194        }
195        else
196        {
197            tmp - Self::FULL
198        }
199    }
200
201    // TODO: make a Trigonometric trait for float and angle ?
202
203    /// `(cos(self), sin(self))`
204    #[inline(always)]
205    pub fn cos_sin(self) -> (T, T)
206    {
207        let (s, c) = self.0.sin_cos();
208        (c, s)
209    }
210    /// `(sin(self), cos(self))`
211    #[inline(always)]
212    pub fn sin_cos(self) -> (T, T) { self.0.sin_cos() }
213    /// `(cos(self), cos(self))`
214    #[inline(always)]
215    pub fn cos_cos(self) -> (T, T)
216    {
217        let c = self.cos();
218        (c, c)
219    }
220    /// `(sin(self), sin(self))`
221    #[inline(always)]
222    pub fn sin_sin(self) -> (T, T)
223    {
224        let s = self.sin();
225        (s, s)
226    }
227
228    /// Cosine of the angle.
229    #[inline(always)]
230    pub fn cos(self) -> T { self.0.cos() }
231    /// Hyperbolic cosine.
232    #[inline(always)]
233    pub fn cosh(self) -> T { self.0.cosh() }
234
235    /// Sine of the angle.
236    #[inline(always)]
237    pub fn sin(self) -> T { self.0.sin() }
238    /// Hyperbolic sine.
239    #[inline(always)]
240    pub fn sinh(self) -> T { self.0.sinh() }
241
242    /// Cotangent (`1 / tan(self)`).
243    #[inline(always)]
244    pub fn cot(self) -> T { self.0.cot() }
245
246    /// Tangent of the angle.
247    #[inline(always)]
248    pub fn tan(self) -> T { self.0.tan() }
249    /// Hyperbolic tangent.
250    #[inline(always)]
251    pub fn tanh(self) -> T { self.0.tanh() }
252
253    /// Return a normalized (length = 1) vector with the same angle
254    #[inline(always)]
255    pub fn to_vec2_normalized(self) -> Vec2
256    where
257        T: Into<float>,
258    {
259        unsafe { Angle::from_inner_value(self.0.into()).to_vector2_normalized() }
260    }
261    #[inline(always)]
262    pub fn to_vec2(self, length: T) -> Vec2
263    where
264        T: Into<float>,
265    {
266        unsafe { Angle::from_inner_value(self.0.into()).to_vector2(length.into()) }
267    }
268
269    /// Return a normalized (length = 1) vector with the same angle
270    #[inline(always)]
271    pub fn to_vector2_normalized(self) -> Vector2<T> { self.to_vector2(T::ONE) }
272    #[inline(always)]
273    pub fn to_vector2(self, length: T) -> Vector2<T>
274    {
275        Vector2::new(self.cos() * length, self.sin() * length)
276    }
277
278    pub fn inside_range(self, begin: Self, end: Self) -> bool
279    {
280        let self_normalized = self.normalized_positive();
281        let begin_normalized = begin.normalized_positive();
282        let end_normalized = end.normalized_positive();
283
284        if begin_normalized.0 <= end_normalized.0
285        {
286            self_normalized.0 >= begin_normalized.0 && self_normalized.0 <= end_normalized.0
287        }
288        else
289        {
290            self_normalized.0 >= begin_normalized.0 || self_normalized.0 <= end_normalized.0
291        }
292    }
293}
294
295impl<T: Float> Debug for AngleOf<T>
296where
297    T: Debug,
298{
299    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{:?}°", self.degree()) }
300}
301impl<T: Float> Display for AngleOf<T>
302where
303    T: Display,
304{
305    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{:}°", self.degree()) }
306}
307
308impl<T: Float> RangeDefault for AngleOf<T>
309{
310    const RANGE_MIN: Self = Self::ZERO;
311    const RANGE_HALF: Self = Self::FLAT;
312    const RANGE_MAX: Self = Self::FULL;
313    const RANGE: Self = Self::FULL;
314}
315
316/*
317impl<T:Float> AngleOf<T>
318{
319    pub fn fmt_degree_with_optional_precision(self, precision : Option<T>) -> DisplayAngleDegree { DisplayAngleDegree { angle: self, precision }}
320    pub fn fmt_degree_with_precision(self, precision : float) -> DisplayAngleDegree { self.fmt_degree_with_optional_precision(Some(precision)) }
321    pub fn fmt_degree(self) -> DisplayAngleDegree { self.fmt_degree_with_optional_precision(None) }
322}
323impl<T:Float> Display for AngleOf<T> where T:Float { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { self.fmt_degree_with_precision(360.).fmt(f) }}
324
325#[derive(Clone, Copy)]
326pub struct DisplayAngleDegree{ angle : AngleOf, precision : Option<T> }
327impl<T:Float> Display for DisplayAngleDegree {
328    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult
329    {
330        write!(f, "{}°",
331        {
332            match self.precision
333            {
334                Some(p) => (self.angle.degree() / p) as i32 as float * p,
335                None => self.angle.degree(),
336            }
337        })
338    }
339}
340*/