bevy_math/
compass.rs

1use crate::Dir2;
2#[cfg(feature = "bevy_reflect")]
3use bevy_reflect::Reflect;
4#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
5use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
6
7/// A compass enum with 4 directions.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
11#[cfg_attr(
12    all(feature = "serialize", feature = "bevy_reflect"),
13    reflect(Deserialize, Serialize)
14)]
15pub enum CompassQuadrant {
16    /// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
17    North,
18    /// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
19    East,
20    /// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
21    South,
22    /// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
23    West,
24}
25
26/// A compass enum with 8 directions.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
30#[cfg_attr(
31    all(feature = "serialize", feature = "bevy_reflect"),
32    reflect(Deserialize, Serialize)
33)]
34pub enum CompassOctant {
35    /// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
36    North,
37    /// Corresponds to [`Dir2::NORTH_EAST`]
38    NorthEast,
39    /// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
40    East,
41    /// Corresponds to [`Dir2::SOUTH_EAST`]
42    SouthEast,
43    /// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
44    South,
45    /// Corresponds to [`Dir2::SOUTH_WEST`]
46    SouthWest,
47    /// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
48    West,
49    /// Corresponds to [`Dir2::NORTH_WEST`]
50    NorthWest,
51}
52
53impl From<CompassQuadrant> for Dir2 {
54    fn from(q: CompassQuadrant) -> Self {
55        match q {
56            CompassQuadrant::North => Dir2::NORTH,
57            CompassQuadrant::East => Dir2::EAST,
58            CompassQuadrant::South => Dir2::SOUTH,
59            CompassQuadrant::West => Dir2::WEST,
60        }
61    }
62}
63
64impl From<Dir2> for CompassQuadrant {
65    /// Converts a [`Dir2`] to a [`CompassQuadrant`] in a lossy manner.
66    /// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
67    fn from(dir: Dir2) -> Self {
68        let angle = dir.to_angle().to_degrees();
69
70        match angle {
71            -135.0..=-45.0 => Self::South,
72            -45.0..=45.0 => Self::East,
73            45.0..=135.0 => Self::North,
74            135.0..=180.0 | -180.0..=-135.0 => Self::West,
75            _ => unreachable!(),
76        }
77    }
78}
79
80impl From<CompassOctant> for Dir2 {
81    fn from(o: CompassOctant) -> Self {
82        match o {
83            CompassOctant::North => Dir2::NORTH,
84            CompassOctant::NorthEast => Dir2::NORTH_EAST,
85            CompassOctant::East => Dir2::EAST,
86            CompassOctant::SouthEast => Dir2::SOUTH_EAST,
87            CompassOctant::South => Dir2::SOUTH,
88            CompassOctant::SouthWest => Dir2::SOUTH_WEST,
89            CompassOctant::West => Dir2::WEST,
90            CompassOctant::NorthWest => Dir2::NORTH_WEST,
91        }
92    }
93}
94
95impl From<Dir2> for CompassOctant {
96    /// Converts a [`Dir2`] to a [`CompassOctant`] in a lossy manner.
97    /// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
98    fn from(dir: Dir2) -> Self {
99        let angle = dir.to_angle().to_degrees();
100
101        match angle {
102            -112.5..=-67.5 => Self::South,
103            -67.5..=-22.5 => Self::SouthEast,
104            -22.5..=22.5 => Self::East,
105            22.5..=67.5 => Self::NorthEast,
106            67.5..=112.5 => Self::North,
107            112.5..=157.5 => Self::NorthWest,
108            157.5..=180.0 | -180.0..=-157.5 => Self::West,
109            -157.5..=-112.5 => Self::SouthWest,
110            _ => unreachable!(),
111        }
112    }
113}
114
115#[cfg(test)]
116mod test_compass_quadrant {
117    use crate::{CompassQuadrant, Dir2, Vec2};
118
119    #[test]
120    fn test_cardinal_directions() {
121        let tests = vec![
122            (
123                Dir2::new(Vec2::new(1.0, 0.0)).unwrap(),
124                CompassQuadrant::East,
125            ),
126            (
127                Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
128                CompassQuadrant::North,
129            ),
130            (
131                Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
132                CompassQuadrant::West,
133            ),
134            (
135                Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
136                CompassQuadrant::South,
137            ),
138        ];
139
140        for (dir, expected) in tests {
141            assert_eq!(CompassQuadrant::from(dir), expected);
142        }
143    }
144
145    #[test]
146    fn test_north_pie_slice() {
147        let tests = vec![
148            (
149                Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
150                CompassQuadrant::North,
151            ),
152            (
153                Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
154                CompassQuadrant::North,
155            ),
156        ];
157
158        for (dir, expected) in tests {
159            assert_eq!(CompassQuadrant::from(dir), expected);
160        }
161    }
162
163    #[test]
164    fn test_east_pie_slice() {
165        let tests = vec![
166            (
167                Dir2::new(Vec2::new(0.9, 0.1)).unwrap(),
168                CompassQuadrant::East,
169            ),
170            (
171                Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
172                CompassQuadrant::East,
173            ),
174        ];
175
176        for (dir, expected) in tests {
177            assert_eq!(CompassQuadrant::from(dir), expected);
178        }
179    }
180
181    #[test]
182    fn test_south_pie_slice() {
183        let tests = vec![
184            (
185                Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
186                CompassQuadrant::South,
187            ),
188            (
189                Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
190                CompassQuadrant::South,
191            ),
192        ];
193
194        for (dir, expected) in tests {
195            assert_eq!(CompassQuadrant::from(dir), expected);
196        }
197    }
198
199    #[test]
200    fn test_west_pie_slice() {
201        let tests = vec![
202            (
203                Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
204                CompassQuadrant::West,
205            ),
206            (
207                Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
208                CompassQuadrant::West,
209            ),
210        ];
211
212        for (dir, expected) in tests {
213            assert_eq!(CompassQuadrant::from(dir), expected);
214        }
215    }
216}
217
218#[cfg(test)]
219mod test_compass_octant {
220    use crate::{CompassOctant, Dir2, Vec2};
221
222    #[test]
223    fn test_cardinal_directions() {
224        let tests = vec![
225            (
226                Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(),
227                CompassOctant::NorthWest,
228            ),
229            (
230                Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
231                CompassOctant::North,
232            ),
233            (
234                Dir2::new(Vec2::new(0.5, 0.5)).unwrap(),
235                CompassOctant::NorthEast,
236            ),
237            (Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassOctant::East),
238            (
239                Dir2::new(Vec2::new(0.5, -0.5)).unwrap(),
240                CompassOctant::SouthEast,
241            ),
242            (
243                Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
244                CompassOctant::South,
245            ),
246            (
247                Dir2::new(Vec2::new(-0.5, -0.5)).unwrap(),
248                CompassOctant::SouthWest,
249            ),
250            (
251                Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
252                CompassOctant::West,
253            ),
254        ];
255
256        for (dir, expected) in tests {
257            assert_eq!(CompassOctant::from(dir), expected);
258        }
259    }
260
261    #[test]
262    fn test_north_pie_slice() {
263        let tests = vec![
264            (
265                Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
266                CompassOctant::North,
267            ),
268            (
269                Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
270                CompassOctant::North,
271            ),
272        ];
273
274        for (dir, expected) in tests {
275            assert_eq!(CompassOctant::from(dir), expected);
276        }
277    }
278
279    #[test]
280    fn test_north_east_pie_slice() {
281        let tests = vec![
282            (
283                Dir2::new(Vec2::new(0.4, 0.6)).unwrap(),
284                CompassOctant::NorthEast,
285            ),
286            (
287                Dir2::new(Vec2::new(0.6, 0.4)).unwrap(),
288                CompassOctant::NorthEast,
289            ),
290        ];
291
292        for (dir, expected) in tests {
293            assert_eq!(CompassOctant::from(dir), expected);
294        }
295    }
296
297    #[test]
298    fn test_east_pie_slice() {
299        let tests = vec![
300            (Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East),
301            (
302                Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
303                CompassOctant::East,
304            ),
305        ];
306
307        for (dir, expected) in tests {
308            assert_eq!(CompassOctant::from(dir), expected);
309        }
310    }
311
312    #[test]
313    fn test_south_east_pie_slice() {
314        let tests = vec![
315            (
316                Dir2::new(Vec2::new(0.4, -0.6)).unwrap(),
317                CompassOctant::SouthEast,
318            ),
319            (
320                Dir2::new(Vec2::new(0.6, -0.4)).unwrap(),
321                CompassOctant::SouthEast,
322            ),
323        ];
324
325        for (dir, expected) in tests {
326            assert_eq!(CompassOctant::from(dir), expected);
327        }
328    }
329
330    #[test]
331    fn test_south_pie_slice() {
332        let tests = vec![
333            (
334                Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
335                CompassOctant::South,
336            ),
337            (
338                Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
339                CompassOctant::South,
340            ),
341        ];
342
343        for (dir, expected) in tests {
344            assert_eq!(CompassOctant::from(dir), expected);
345        }
346    }
347
348    #[test]
349    fn test_south_west_pie_slice() {
350        let tests = vec![
351            (
352                Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(),
353                CompassOctant::SouthWest,
354            ),
355            (
356                Dir2::new(Vec2::new(-0.6, -0.4)).unwrap(),
357                CompassOctant::SouthWest,
358            ),
359        ];
360
361        for (dir, expected) in tests {
362            assert_eq!(CompassOctant::from(dir), expected);
363        }
364    }
365
366    #[test]
367    fn test_west_pie_slice() {
368        let tests = vec![
369            (
370                Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
371                CompassOctant::West,
372            ),
373            (
374                Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
375                CompassOctant::West,
376            ),
377        ];
378
379        for (dir, expected) in tests {
380            assert_eq!(CompassOctant::from(dir), expected);
381        }
382    }
383
384    #[test]
385    fn test_north_west_pie_slice() {
386        let tests = vec![
387            (
388                Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(),
389                CompassOctant::NorthWest,
390            ),
391            (
392                Dir2::new(Vec2::new(-0.6, 0.4)).unwrap(),
393                CompassOctant::NorthWest,
394            ),
395        ];
396
397        for (dir, expected) in tests {
398            assert_eq!(CompassOctant::from(dir), expected);
399        }
400    }
401}