cogs_gamedev/grids/
directions.rs

1use super::ICoord;
2
3use enum_map::Enum;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7/// Four-way directions.
8///
9/// These start at North and increment counter-clockwise,
10/// so you can convert them to integers with `as` and use them
11/// in rotational calculations if you need.
12#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Enum)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum Direction4 {
15    North,
16    East,
17    South,
18    West,
19}
20
21impl Direction4 {
22    /// All the directions in order.
23    /// This is used internally for rotations and flips.
24    /// I made it public just in case it's helpful for you the programmer.
25    pub const DIRECTIONS: [Direction4; 4] = [
26        Direction4::North,
27        Direction4::East,
28        Direction4::South,
29        Direction4::West,
30    ];
31
32    /// Rotate this by the given amount.
33    ///
34    /// ```
35    /// # use cogs_gamedev::grids::{Direction4, Rotation};
36    /// use Direction4::*;
37    /// use Rotation::*;
38    ///
39    /// assert_eq!(North.rotate(Clockwise), East);
40    /// assert_eq!(North.rotate(CounterClockwise), West);
41    /// ```
42    pub fn rotate(self, rot: Rotation) -> Self {
43        self.rotate_by(rot.steps_clockwise())
44    }
45
46    /// Get this direction, rotated by this many steps clockwise.
47    /// Negative numbers go counter-clockwise.
48    ///
49    /// ```
50    /// # use cogs_gamedev::grids::Direction4;
51    /// use Direction4::*;
52    /// assert_eq!(North.rotate_by(1), East);
53    /// assert_eq!(North.rotate_by(2), South);
54    /// assert_eq!(North.rotate_by(-1), West);
55    /// assert_eq!(North.rotate_by(5).rotate_by(-11), South);
56    /// ```
57    pub fn rotate_by(self, steps_clockwise: isize) -> Self {
58        let idx = self as isize;
59        let new_idx =
60            ((idx + steps_clockwise).rem_euclid(Self::DIRECTIONS.len() as isize)) as usize;
61        Self::DIRECTIONS[new_idx]
62    }
63
64    /// Flip this direction.
65    ///
66    /// ```
67    /// # use cogs_gamedev::grids::Direction4;
68    /// use Direction4::*;
69    /// assert_eq!(North.flip(), South);
70    /// assert_eq!(West.flip(), East);
71    /// assert_eq!(East.flip().flip(), East);
72    /// ```
73    pub fn flip(self) -> Self {
74        self.rotate_by(2)
75    }
76
77    /// Get this direction in radians.
78    ///
79    /// This uses trigonometric + graphical standard, where:
80    /// - 0 radians is to the right
81    /// - Positive radians increment *clockwise*. NOTE: this is opposite from normal trig,
82    /// but makes sense in computer graphics where +Y is downwards.
83    ///
84    /// If you need it in degrees just call `.to_degrees` on the result.
85    ///
86    /// ```
87    /// # use cogs_gamedev::grids::Direction4;
88    /// use Direction4::*;
89    /// use std::f32::consts::TAU;
90    ///
91    /// let north_radians = North.radians();
92    /// assert!((north_radians - (TAU / 4.0 * 3.0)).abs() < 1e-10);
93    ///
94    /// let west_radians = West.radians();
95    /// assert!((west_radians - (TAU / 4.0 * 2.0)).abs() < 1e-10);
96    ///
97    /// ```
98    pub fn radians(self) -> f32 {
99        ((self as i8) - 1).rem_euclid(4) as f32 * std::f32::consts::TAU / 4.0
100    }
101
102    /// Get the deltas a step in this direction would result in, as a ICoord.
103    ///
104    /// ```
105    /// # use cogs_gamedev::grids::Direction4;
106    /// # use cogs_gamedev::grids::ICoord;
107    /// use Direction4::*;
108    ///
109    /// assert_eq!(North.deltas(), ICoord {x: 0, y: -1});
110    /// assert_eq!(West.deltas(), ICoord {x: -1, y: 0});
111    /// ```
112    pub fn deltas(self) -> ICoord {
113        let (x, y) = match self {
114            Direction4::North => (0, -1),
115            Direction4::East => (1, 0),
116            Direction4::South => (0, 1),
117            Direction4::West => (-1, 0),
118        };
119        ICoord { x, y }
120    }
121
122    /// See if this direction points horizontally (ie, is `East` or `West`).
123    ///
124    /// ```
125    /// # use cogs_gamedev::grids::Direction4;
126    /// use Direction4::*;
127    ///
128    /// assert!(East.is_horizontal());
129    /// assert!(!South.is_horizontal());
130    /// ```
131    pub fn is_horizontal(self) -> bool {
132        matches!(self, Direction4::East | Direction4::West)
133    }
134
135    /// See if this direction points vertically (ie, is `North` or `South`).
136    ///
137    /// ```
138    /// # use cogs_gamedev::grids::Direction4;
139    /// use Direction4::*;
140    ///
141    /// assert!(North.is_vertical());
142    /// assert!(!West.is_vertical());
143    /// ```
144    pub fn is_vertical(self) -> bool {
145        matches!(self, Direction4::North | Direction4::South)
146    }
147}
148
149/// Eight-way directions.
150///
151/// These start at North and increment counter-clockwise,
152/// so you can convert them to integers with `as` and use them
153/// in rotational calculations if you need.
154#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Enum)]
155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
156pub enum Direction8 {
157    North,
158    NorthEast,
159    East,
160    SouthEast,
161    South,
162    SouthWest,
163    West,
164    NorthWest,
165}
166
167impl Direction8 {
168    /// All the directions in order.
169    /// This is used internally for rotations and flips.
170    /// I made it public just in case it's helpful for you the programmer.
171    pub const DIRECTIONS: [Direction8; 8] = [
172        Direction8::North,
173        Direction8::NorthEast,
174        Direction8::East,
175        Direction8::SouthEast,
176        Direction8::South,
177        Direction8::SouthWest,
178        Direction8::West,
179        Direction8::NorthWest,
180    ];
181
182    /// Rotate this by the given amount.
183    ///
184    /// ```
185    /// # use cogs_gamedev::grids::{Direction8, Rotation};
186    /// use Direction8::*;
187    /// use Rotation::*;
188    ///
189    /// assert_eq!(NorthEast.rotate(Clockwise), East);
190    /// assert_eq!(South.rotate(CounterClockwise), SouthEast);
191    /// ```
192    pub fn rotate(self, rot: Rotation) -> Self {
193        self.rotate_by(rot.steps_clockwise())
194    }
195
196    /// Get this direction, rotated by this many steps clockwise.
197    /// Negative numbers go counter-clockwise.
198    ///
199    /// ```
200    /// # use cogs_gamedev::grids::Direction8;
201    /// use Direction8::*;
202    /// let north = North;
203    /// assert_eq!(north.rotate_by(1), NorthEast);
204    /// assert_eq!(north.rotate_by(2), East);
205    /// assert_eq!(north.rotate_by(-1), NorthWest);
206    /// assert_eq!(north.rotate_by(4), South);
207    /// assert_eq!(north.rotate_by(5).rotate_by(-11), East);
208    /// ```
209    pub fn rotate_by(self, steps_clockwise: isize) -> Self {
210        let idx = self as isize;
211        let new_idx =
212            ((idx + steps_clockwise).rem_euclid(Self::DIRECTIONS.len() as isize)) as usize;
213        Self::DIRECTIONS[new_idx]
214    }
215
216    /// Flip this direction.
217    ///
218    /// ```
219    /// # use cogs_gamedev::grids::Direction8;
220    /// use Direction8::*;
221    /// assert_eq!(North.flip(), South);
222    /// assert_eq!(West.flip(), East);
223    /// assert_eq!(SouthWest.flip(), NorthEast);
224    /// assert_eq!(East.flip().flip(), East);
225    /// ```
226    pub fn flip(self) -> Self {
227        self.rotate_by(4)
228    }
229
230    /// Get this direction in radians.
231    ///
232    /// This uses trigonometric + graphical standard, where:
233    /// - 0 radians is to the right
234    /// - Positive radians increment *clockwise*. NOTE: this is opposite from normal trig,
235    /// but makes sense in computer graphics where +Y is downwards.
236    ///
237    /// If you need it in degrees just call `.to_degrees` on the result.
238    ///
239    /// ```
240    /// # use cogs_gamedev::grids::Direction8;
241    /// use Direction8::*;
242    /// use std::f32::consts::TAU;
243    ///
244    /// let north_radians = North.radians();
245    /// assert!((north_radians - (TAU / 8.0 * 6.0)).abs() < 1e-10);
246    ///
247    /// let west_radians = West.radians();
248    /// assert!((west_radians - (TAU / 8.0 * 4.0)).abs() < 1e-10);
249    ///
250    /// let southeast_radians = SouthEast.radians();
251    /// assert!((southeast_radians - (TAU / 8.0)).abs() < 1e-10);
252    ///
253    /// ```
254    pub fn radians(self) -> f32 {
255        ((self as i8) - 2).rem_euclid(8) as f32 * std::f32::consts::TAU / 8.0
256    }
257
258    /// Get the deltas a step in this direction would result in,
259    /// as an ICoord.
260    ///
261    /// ```
262    /// # use cogs_gamedev::grids::Direction8;
263    /// # use cogs_gamedev::grids::ICoord;
264    /// use Direction8::*;
265    ///
266    /// assert_eq!(East.deltas(), ICoord {x: 1, y: 0});
267    /// assert_eq!(NorthWest.deltas(), ICoord {x: -1, y: -1});
268    /// ```
269    pub fn deltas(self) -> ICoord {
270        let (x, y) = match self {
271            Direction8::North => (0, -1),
272            Direction8::NorthEast => (1, -1),
273            Direction8::East => (1, 0),
274            Direction8::SouthEast => (1, 1),
275            Direction8::South => (0, 1),
276            Direction8::SouthWest => (-1, 1),
277            Direction8::West => (-1, 0),
278            Direction8::NorthWest => (-1, -1),
279        };
280        ICoord { x, y }
281    }
282}
283
284/// 2-way rotations: clockwise or counterclockwise.
285/// These don't indicate any specific angle by themselves, only in relation to something.
286#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Enum)]
287#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
288pub enum Rotation {
289    Clockwise,
290    CounterClockwise,
291}
292
293impl Rotation {
294    /// Get the number of steps clockwise this does.
295    /// - `Clockwise` is 1
296    /// - `CounterClockwise` is -1
297    pub fn steps_clockwise(&self) -> isize {
298        match self {
299            Rotation::Clockwise => 1,
300            Rotation::CounterClockwise => -1,
301        }
302    }
303}