aglet/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod area;
4mod direction;
5mod grid;
6mod lines;
7
8pub use area::*;
9pub use direction::*;
10pub use grid::*;
11pub use lines::*;
12
13pub use enumflags2::{BitFlag, BitFlags};
14
15use std::{
16    convert::{TryFrom, TryInto},
17    fmt::Display,
18    num::TryFromIntError,
19    ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
20};
21
22/// Unsigned-int coordinates
23#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Coord {
26    pub x: u32,
27    pub y: u32,
28}
29
30impl Coord {
31    pub const ZERO: Coord = Coord::new(0, 0);
32
33    /// Make a new coord.
34    pub const fn new(x: u32, y: u32) -> Self {
35        Self { x, y }
36    }
37
38    /// Get this as an index into an array representing a 2d array.
39    ///
40    /// (AKA, `y * width + x`.)
41    pub fn to_2d_idx(self, width: u32) -> u32 {
42        // what did you think i was kidding or something
43        self.y * width + self.x
44    }
45
46    /// Convert this into an ICoord.
47    pub fn to_icoord(self) -> CoordVec {
48        self.into()
49    }
50
51    /// Get a list of this coordinate's orthagonal neighbors.
52    /// They are given in clockwise order starting with the neighbor to the north,
53    /// as if each of [`Direction4::DIRECTIONS`] had been added to them.
54    ///
55    /// If a neighbor is out of bounds, it is skipped in the output.
56    ///
57    /// There may be 2, 3, or 4 neighbors:
58    /// - 2 if this is at `(0, 0)`
59    /// - 3 if this is on an edge (`x` or `y` are 0)
60    /// - 4 otherwise.
61    ///
62    /// [`Direction4::DIRECTIONS`]: super::Direction4::DIRECTIONS
63    pub fn neighbors4(self) -> Vec<Coord> {
64        Direction4::DIRECTIONS
65            .iter()
66            .filter_map(|dir| {
67                let iself = self.to_icoord();
68                let ineighbor = iself + *dir;
69                ineighbor.to_coord() // conveniently already returns an option.
70            })
71            .collect()
72    }
73
74    /// Get a list of this coordinate's orthagonal and diagonal neighbors.
75    /// They are given in clockwise order starting with the neighbor to the north,
76    /// as if each of [`Direction8::DIRECTIONS`] had been added to them.
77    ///
78    /// If a neighbor is out of bounds, it is skipped in the output.
79    ///
80    /// There may be 3, 5, or 8 neighbors:
81    /// - 3 if this is at `(0, 0)`
82    /// - 5 if this is on an edge (`x` or `y` are 0)
83    /// - 8 otherwise.
84    /// [`Direction8::DIRECTIONS`]: super::Direction8::DIRECTIONS
85    pub fn neighbors8(self) -> Vec<Coord> {
86        Direction8::DIRECTIONS
87            .iter()
88            .filter_map(|dir| {
89                let iself = self.to_icoord();
90                let ineighbor = iself + *dir;
91                ineighbor.to_coord() // conveniently already returns an option.
92            })
93            .collect()
94    }
95
96    pub fn area(self, width: u32, height: u32) -> Area {
97        Area::new(self, width, height)
98    }
99
100    pub fn offset4(self, offset: Direction4) -> Option<Self> {
101        let deltas = offset.deltas();
102        let x = (self.x as i32 + deltas.x).try_into().ok()?;
103        let y = (self.y as i32 + deltas.y).try_into().ok()?;
104        Some(Coord::new(x, y))
105    }
106
107    pub fn offset8(self, offset: Direction8) -> Option<Self> {
108        let deltas = offset.deltas();
109        let x = (self.x as i32 + deltas.x).try_into().ok()?;
110        let y = (self.y as i32 + deltas.y).try_into().ok()?;
111        Some(Coord::new(x, y))
112    }
113
114    pub fn offset9(self, offset: Direction9) -> Option<Self> {
115        let deltas = offset.deltas();
116        let x = (self.x as i32 + deltas.x).try_into().ok()?;
117        let y = (self.y as i32 + deltas.y).try_into().ok()?;
118        Some(Coord::new(x, y))
119    }
120}
121
122impl Add for Coord {
123    type Output = Self;
124    fn add(self, rhs: Self) -> Self::Output {
125        Self {
126            x: self.x + rhs.x,
127            y: self.y + rhs.y,
128        }
129    }
130}
131
132impl AddAssign for Coord {
133    fn add_assign(&mut self, rhs: Self) {
134        self.x += rhs.x;
135        self.y += rhs.y;
136    }
137}
138
139impl Sub for Coord {
140    type Output = Self;
141    fn sub(self, rhs: Self) -> Self::Output {
142        Self {
143            x: self.x - rhs.x,
144            y: self.y - rhs.y,
145        }
146    }
147}
148
149impl SubAssign for Coord {
150    fn sub_assign(&mut self, rhs: Self) {
151        self.x -= rhs.x;
152        self.y -= rhs.y;
153    }
154}
155
156impl Mul<u32> for Coord {
157    type Output = Self;
158    fn mul(self, rhs: u32) -> Self::Output {
159        Self {
160            x: self.x * rhs,
161            y: self.y * rhs,
162        }
163    }
164}
165
166impl MulAssign<u32> for Coord {
167    fn mul_assign(&mut self, rhs: u32) {
168        self.x *= rhs;
169        self.y *= rhs;
170    }
171}
172
173impl Mul<Coord> for Coord {
174    type Output = Self;
175    fn mul(self, rhs: Coord) -> Self::Output {
176        Self {
177            x: self.x * rhs.x,
178            y: self.y * rhs.y,
179        }
180    }
181}
182
183impl MulAssign<Coord> for Coord {
184    fn mul_assign(&mut self, rhs: Coord) {
185        self.x *= rhs.x;
186        self.y *= rhs.y;
187    }
188}
189
190/// Try to convert an ICoord to a Coord.
191/// Will return Error if the ICoord has any negatives in it.
192impl TryFrom<CoordVec> for Coord {
193    type Error = TryFromIntError;
194    fn try_from(value: CoordVec) -> Result<Self, Self::Error> {
195        Ok(Self {
196            x: value.x.try_into()?,
197            y: value.y.try_into()?,
198        })
199    }
200}
201
202impl Display for Coord {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "({}, {})", self.x, self.y)
205    }
206}
207
208/// Signed-int coordinates
209#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub struct CoordVec {
212    pub x: i32,
213    pub y: i32,
214}
215
216impl CoordVec {
217    /// Create a new ICoord
218    pub fn new(x: i32, y: i32) -> Self {
219        Self { x, y }
220    }
221
222    /// Return the quadrant this coordinate is in.
223    ///
224    /// - 1: +X, +Y
225    /// - 2: -X, +Y
226    /// - 3: -X, -Y
227    /// - 4: +X, -Y
228    ///
229    /// Zeroes are treated as positive.
230    pub fn quadrant(self) -> u32 {
231        match (self.x >= 0, self.y >= 0) {
232            (true, true) => 1,
233            (false, true) => 2,
234            (false, false) => 3,
235            (true, false) => 4,
236        }
237    }
238
239    /// Try to convert this to a Coord.
240    /// Returns `None` in case any part is negative.
241    pub fn to_coord(self) -> Option<Coord> {
242        self.try_into().ok()
243    }
244
245    /// Get a list of this coordinate's orthagonal neighbors.
246    /// They are given in clockwise order starting with the neighbor to the north,
247    /// as if each of [`Direction4::DIRECTIONS`] had been added to them.
248    ///
249    /// [`Direction4::DIRECTIONS`]: super::Direction4::DIRECTIONS
250    pub fn neighbors4(self) -> [CoordVec; 4] {
251        [
252            self + Direction4::North,
253            self + Direction4::East,
254            self + Direction4::South,
255            self + Direction4::West,
256        ]
257    }
258
259    /// Get a list of this coordinate's orthagonal and diagonal neighbors.
260    /// They are given in clockwise order starting with the neighbor to the north,
261    /// as if each of [`Direction8::DIRECTIONS`] had been added to them.
262    ///
263    /// [`Direction8::DIRECTIONS`]: super::Direction8::DIRECTIONS
264    pub fn neighbors8(self) -> [CoordVec; 8] {
265        [
266            self + Direction8::North,
267            self + Direction8::NorthEast,
268            self + Direction8::East,
269            self + Direction8::SouthEast,
270            self + Direction8::South,
271            self + Direction8::SouthWest,
272            self + Direction8::West,
273            self + Direction8::NorthWest,
274        ]
275    }
276
277    /// Turn this into the closest Direction9 it is pointing in.
278    ///
279    /// This uses the convention that north is positive Y.
280    pub fn point9(self) -> Direction9 {
281        if self.x == 0 && self.y == 0 {
282            return Direction9::Center;
283        }
284        // there's gotta be a better way to do this
285        let angle =
286            (-self.y as f32).atan2(self.x as f32) + std::f32::consts::PI;
287        match angle / std::f32::consts::TAU * 16.0 {
288            a if a < 1.0 => Direction9::East,
289            a if a < 3.0 => Direction9::NorthEast,
290            a if a < 5.0 => Direction9::North,
291            a if a < 7.0 => Direction9::NorthWest,
292            a if a < 9.0 => Direction9::West,
293            a if a < 11.0 => Direction9::SouthWest,
294            a if a < 13.0 => Direction9::South,
295            a if a < 15.0 => Direction9::SouthEast,
296            _ => Direction9::East,
297        }
298    }
299}
300
301impl Add for CoordVec {
302    type Output = Self;
303    fn add(self, rhs: Self) -> Self::Output {
304        Self {
305            x: self.x + rhs.x,
306            y: self.y + rhs.y,
307        }
308    }
309}
310
311impl AddAssign for CoordVec {
312    fn add_assign(&mut self, rhs: Self) {
313        self.x += rhs.x;
314        self.y += rhs.y;
315    }
316}
317
318impl Sub for CoordVec {
319    type Output = Self;
320    fn sub(self, rhs: Self) -> Self::Output {
321        Self {
322            x: self.x - rhs.x,
323            y: self.y - rhs.y,
324        }
325    }
326}
327
328impl SubAssign for CoordVec {
329    fn sub_assign(&mut self, rhs: Self) {
330        self.x -= rhs.x;
331        self.y -= rhs.y;
332    }
333}
334
335impl Add<Direction4> for CoordVec {
336    type Output = Self;
337    fn add(self, rhs: Direction4) -> Self::Output {
338        self + rhs.deltas()
339    }
340}
341
342impl AddAssign<Direction4> for CoordVec {
343    fn add_assign(&mut self, rhs: Direction4) {
344        *self += rhs.deltas();
345    }
346}
347
348impl Add<Direction8> for CoordVec {
349    type Output = Self;
350    fn add(self, rhs: Direction8) -> Self::Output {
351        self + rhs.deltas()
352    }
353}
354
355impl AddAssign<Direction8> for CoordVec {
356    fn add_assign(&mut self, rhs: Direction8) {
357        *self += rhs.deltas();
358    }
359}
360
361impl Mul<i32> for CoordVec {
362    type Output = Self;
363    fn mul(self, rhs: i32) -> Self::Output {
364        Self {
365            x: self.x * rhs,
366            y: self.y * rhs,
367        }
368    }
369}
370
371impl MulAssign<i32> for CoordVec {
372    fn mul_assign(&mut self, rhs: i32) {
373        self.x *= rhs;
374        self.y *= rhs;
375    }
376}
377
378impl From<Coord> for CoordVec {
379    fn from(value: Coord) -> Self {
380        Self {
381            x: value.x as i32,
382            y: value.y as i32,
383        }
384    }
385}
386
387impl Display for CoordVec {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        write!(f, "({}, {})", self.x, self.y)
390    }
391}