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*/