lattice_graph/hex/axial_based/
shapes.rs

1#[cfg(feature = "const-generic-wrap")]
2use const_generic_wrap::WrapUSIZE;
3use std::marker::PhantomData;
4
5use crate::{
6    hex::shapes::*,
7    lattice_abstract::{Axis, Coordinate, Offset, Shape},
8};
9
10pub trait AxialCoord<I = isize>: Clone + Coordinate {
11    ///Creates a new Coordinate.
12    fn new(r: I, q: I) -> Self;
13    /// Get a reference to the hex axial's r.
14    fn r(&self) -> I;
15    /// Get a reference to the hex axial's q.
16    fn q(&self) -> I;
17}
18
19/// Axial based coordinates for hex graph.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
21pub struct HexAxial {
22    pub(crate) r: isize,
23    pub(crate) q: isize,
24}
25
26impl HexAxial {
27    /// Get a reference to the hex axial's r.
28    pub fn r(&self) -> isize {
29        self.r
30    }
31
32    /// Get a reference to the hex axial's q.
33    pub fn q(&self) -> isize {
34        self.q
35    }
36}
37
38impl HexAxial {
39    ///Creates a new Coordinate.
40    pub fn new(r: isize, q: isize) -> Self {
41        Self { q, r }
42    }
43}
44
45impl Coordinate for HexAxial {}
46impl AxialCoord for HexAxial {
47    #[inline]
48    fn new(r: isize, q: isize) -> Self {
49        Self::new(r, q)
50    }
51
52    #[inline]
53    fn r(&self) -> isize {
54        self.r
55    }
56
57    #[inline]
58    fn q(&self) -> isize {
59        self.q
60    }
61}
62
63/// Defines wheter the hex graph is `flat-top` or `point-top` and is odd or even.
64pub trait HexAxialShapeBase<HA: AxialCoord>: OE + RQ + Clone {
65    type Axis: Axis;
66    unsafe fn move_coord_unchecked(coord: HA, dir: <Self::Axis as Axis>::Direction) -> HA;
67}
68
69impl<HA: AxialCoord> HexAxialShapeBase<HA> for OddR {
70    type Axis = AxisR;
71
72    unsafe fn move_coord_unchecked(coord: HA, dir: AxisDR) -> HA {
73        move_coord_r(coord, dir)
74    }
75}
76
77impl<HA: AxialCoord> HexAxialShapeBase<HA> for EvenR {
78    type Axis = AxisR;
79
80    unsafe fn move_coord_unchecked(coord: HA, dir: AxisDR) -> HA {
81        move_coord_r(coord, dir)
82    }
83}
84
85impl<HA: AxialCoord> HexAxialShapeBase<HA> for OddQ {
86    type Axis = AxisQ;
87
88    unsafe fn move_coord_unchecked(coord: HA, dir: AxisDQ) -> HA {
89        move_coord_q(coord, dir)
90    }
91}
92
93impl<HA: AxialCoord> HexAxialShapeBase<HA> for EvenQ {
94    type Axis = AxisQ;
95
96    unsafe fn move_coord_unchecked(coord: HA, dir: AxisDQ) -> HA {
97        move_coord_q(coord, dir)
98    }
99}
100
101impl<T, A, HA: AxialCoord> HexAxialShapeBase<HA> for DirectedMarker<T>
102where
103    T: HexAxialShapeBase<HA, Axis = A>,
104    A: Axis,
105    A::Direction: Axis<Direction = A::Direction>,
106{
107    type Axis = A::Direction;
108
109    unsafe fn move_coord_unchecked(coord: HA, dir: A::Direction) -> HA {
110        T::move_coord_unchecked(coord, dir)
111    }
112}
113
114fn move_coord_r<HA: AxialCoord>(coord: HA, dir: AxisDR) -> HA {
115    match dir {
116        AxisDR::NE => HA::new(coord.r(), coord.q() + 1),
117        AxisDR::E => HA::new(coord.r() + 1, coord.q()),
118        AxisDR::SE => HA::new(coord.r() + 1, coord.q() - 1),
119        AxisDR::SW => HA::new(coord.r(), coord.q() - 1),
120        AxisDR::W => HA::new(coord.r() - 1, coord.q()),
121        AxisDR::NW => HA::new(coord.r() - 1, coord.q() + 1),
122    }
123}
124
125fn move_coord_q<HA: AxialCoord>(coord: HA, dir: AxisDQ) -> HA {
126    match dir {
127        AxisDQ::N => HA::new(coord.r(), coord.q() + 1),
128        AxisDQ::NE => HA::new(coord.r() + 1, coord.q()),
129        AxisDQ::SE => HA::new(coord.r() + 1, coord.q() - 1),
130        AxisDQ::S => HA::new(coord.r(), coord.q() - 1),
131        AxisDQ::SW => HA::new(coord.r() - 1, coord.q()),
132        AxisDQ::NW => HA::new(coord.r() - 1, coord.q() + 1),
133    }
134}
135
136/// Shape for Axial based coordinates.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
138pub struct HexAxialShape<ShapeBase, Loop, H = usize, V = usize, HA = HexAxial> {
139    h: H,
140    v: V,
141    l: PhantomData<fn() -> Loop>,
142    t: PhantomData<fn() -> ShapeBase>,
143    ha: PhantomData<fn() -> HA>,
144}
145
146impl<ShapeBase, Loop, H, V, HA> HexAxialShape<ShapeBase, Loop, H, V, HA> {
147    /// Create a new shape.
148    pub fn new(h: H, v: V) -> Self {
149        Self {
150            h,
151            v,
152            l: PhantomData,
153            t: PhantomData,
154            ha: PhantomData,
155        }
156    }
157
158    #[inline]
159    fn convert<L2>(&self) -> HexAxialShape<ShapeBase, L2, H, V, HA>
160    where
161        H: Clone,
162        V: Clone,
163    {
164        HexAxialShape {
165            h: self.h.clone(),
166            v: self.v.clone(),
167            l: PhantomData,
168            t: PhantomData,
169            ha: PhantomData,
170        }
171    }
172}
173
174/// Shape for Axial based coordinates with const size. This is ZST.
175#[cfg(feature = "const-generic-wrap")]
176pub type ConstHexAxialShape<T, L, const H: usize, const V: usize> =
177    HexAxialShape<T, L, WrapUSIZE<H>, WrapUSIZE<V>>;
178
179#[cfg(feature = "const-generic-wrap")]
180impl<T, L, const H: usize, const V: usize> Default for ConstHexAxialShape<T, L, H, V> {
181    fn default() -> Self {
182        Self::new(WrapUSIZE::<H>, WrapUSIZE::<V>)
183    }
184}
185
186impl<B, H, V, HA> Shape for HexAxialShape<B, (), H, V, HA>
187where
188    HA: AxialCoord,
189    B: HexAxialShapeBase<HA>,
190    H: Clone + Into<usize>,
191    V: Clone + Into<usize>,
192{
193    type Axis = B::Axis;
194    type Coordinate = HA;
195    type OffsetConvertError = ();
196    type CoordinateMoveError = ();
197
198    fn horizontal(&self) -> usize {
199        self.h.clone().into()
200    }
201
202    fn vertical(&self) -> usize {
203        self.v.clone().into()
204    }
205
206    fn to_offset(&self, coord: Self::Coordinate) -> Result<Offset, Self::OffsetConvertError> {
207        if B::IS_FLAT_TOP {
208            if (coord.r() as usize) < self.horizontal() {
209                let v = coord.q() + ((coord.r() as usize + B::CONVERT_OFFSET) / 2) as isize;
210                if (v as usize) < self.vertical() {
211                    return Ok(Offset::new(coord.r() as usize, v as usize));
212                }
213            }
214        } else {
215            // coord.q() < 0 => (coord.q() as usize) > usize::MAX >= self.vertical()
216            if (coord.q() as usize) < self.vertical() {
217                let h = coord.r() + ((coord.q() as usize + B::CONVERT_OFFSET) / 2) as isize;
218                if (h as usize) < self.horizontal() {
219                    return Ok(Offset::new(h as usize, coord.q() as usize));
220                }
221            }
222        }
223        Err(())
224    }
225
226    unsafe fn to_offset_unchecked(&self, coord: Self::Coordinate) -> Offset {
227        if B::IS_FLAT_TOP {
228            let v = coord.q() + ((coord.r() as usize + B::CONVERT_OFFSET) / 2) as isize;
229            Offset::new(coord.r() as usize, v as usize)
230        } else {
231            let h = coord.r() + ((coord.q() as usize + B::CONVERT_OFFSET) / 2) as isize;
232            Offset::new(h as usize, coord.q() as usize)
233        }
234    }
235
236    fn offset_to_coordinate(&self, offset: crate::lattice_abstract::Offset) -> Self::Coordinate {
237        if B::IS_FLAT_TOP {
238            HA::new(
239                offset.horizontal() as isize,
240                offset.vertical() as isize
241                    - ((offset.horizontal() + B::CONVERT_OFFSET) / 2) as isize,
242            )
243        } else {
244            HA::new(
245                offset.horizontal() as isize
246                    - ((offset.vertical() + B::CONVERT_OFFSET) / 2) as isize,
247                offset.vertical() as isize,
248            )
249        }
250    }
251
252    fn horizontal_edge_size(&self, _axis: Self::Axis) -> usize {
253        self.horizontal()
254    }
255
256    fn vertical_edge_size(&self, _axis: Self::Axis) -> usize {
257        self.vertical()
258    }
259
260    fn move_coord(
261        &self,
262        coord: Self::Coordinate,
263        dir: <Self::Axis as Axis>::Direction,
264    ) -> Result<Self::Coordinate, Self::CoordinateMoveError> {
265        let c = unsafe { B::move_coord_unchecked(coord, dir) };
266        if self.to_offset(c).is_ok() {
267            Ok(c)
268        } else {
269            Err(())
270        }
271    }
272
273    unsafe fn move_coord_unchecked(
274        &self,
275        coord: Self::Coordinate,
276        dir: <Self::Axis as Axis>::Direction,
277    ) -> Self::Coordinate {
278        B::move_coord_unchecked(coord, dir)
279    }
280}
281
282impl<B, H, V, HA> Shape for HexAxialShape<B, LoopEW, H, V, HA>
283where
284    HA: AxialCoord,
285    B: HexAxialShapeBase<HA>,
286    H: Clone + Into<usize>,
287    V: Clone + Into<usize>,
288{
289    type Axis = B::Axis;
290    type Coordinate = HA;
291    type OffsetConvertError = ();
292    type CoordinateMoveError = ();
293
294    fn horizontal(&self) -> usize {
295        self.h.clone().into()
296    }
297
298    fn vertical(&self) -> usize {
299        self.v.clone().into()
300    }
301
302    #[inline]
303    fn to_offset(&self, coord: Self::Coordinate) -> Result<Offset, Self::OffsetConvertError> {
304        self.convert::<()>().to_offset(coord)
305    }
306
307    #[inline]
308    unsafe fn to_offset_unchecked(&self, coord: Self::Coordinate) -> Offset {
309        self.convert::<()>().to_offset_unchecked(coord)
310    }
311
312    #[inline]
313    fn offset_to_coordinate(&self, offset: crate::lattice_abstract::Offset) -> Self::Coordinate {
314        self.convert::<()>().offset_to_coordinate(offset)
315    }
316
317    fn horizontal_edge_size(&self, _axis: Self::Axis) -> usize {
318        self.horizontal()
319    }
320
321    fn vertical_edge_size(&self, _axis: Self::Axis) -> usize {
322        self.vertical()
323    }
324
325    fn move_coord(
326        &self,
327        coord: Self::Coordinate,
328        dir: <Self::Axis as Axis>::Direction,
329    ) -> Result<Self::Coordinate, Self::CoordinateMoveError> {
330        let c = unsafe { B::move_coord_unchecked(coord, dir) };
331        let q = c.q();
332        if (q as usize) >= self.vertical() {
333            return Err(());
334        }
335        let min = -((q + B::CONVERT_OFFSET as isize) / 2);
336        let h = self.horizontal() as isize;
337        let mut r = c.r();
338        if r < min {
339            while {
340                r += h;
341                r < min
342            } {}
343        } else {
344            let max = h - min;
345            while r >= max {
346                r -= h;
347            }
348        }
349
350        Ok(HA::new(r, q))
351    }
352}