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}