1use crate::{
2 CellIndex, Edge, NUM_HEX_VERTS, NUM_PENT_VERTS, Vertex, coord::CoordIJK,
3 error,
4};
5use core::{fmt, num::NonZeroU8};
6
7const MAX: u8 = 6;
9
10const TO_VERTEX_HEXAGON: [Vertex; NUM_HEX_VERTS as usize] = [
12 Vertex::new_unchecked(3),
13 Vertex::new_unchecked(1),
14 Vertex::new_unchecked(2),
15 Vertex::new_unchecked(5),
16 Vertex::new_unchecked(4),
17 Vertex::new_unchecked(0),
18];
19
20const TO_VERTEX_PENTAGON: [Vertex; NUM_PENT_VERTS as usize] = [
22 Vertex::new_unchecked(1),
23 Vertex::new_unchecked(2),
24 Vertex::new_unchecked(4),
25 Vertex::new_unchecked(3),
26 Vertex::new_unchecked(0),
27];
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
51#[repr(u8)]
52#[expect(clippy::exhaustive_enums, reason = "not gonna change any time soon")]
53#[cfg_attr(
54 feature = "serde",
55 derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
56)]
57pub enum Direction {
58 Center = 0,
60 K = 1,
62 J = 2,
64 JK = 3,
66 I = 4,
68 IK = 5,
70 IJ = 6,
72}
73
74impl Direction {
75 pub fn iter() -> impl Iterator<Item = Self> {
85 (0..=MAX).map(Self::new_unchecked)
87 }
88
89 pub(crate) fn coordinate(self) -> CoordIJK {
91 let value = u8::from(self);
92
93 CoordIJK::new(
94 i32::from((value >> 2) & 1),
95 i32::from((value >> 1) & 1),
96 i32::from(value & 1),
97 )
98 }
99
100 pub(crate) fn axe(self) -> Option<NonZeroU8> {
102 NonZeroU8::new(self.into())
103 }
104
105 #[expect(unsafe_code, reason = "only used internally")]
111 pub(crate) const fn new_unchecked(value: u8) -> Self {
112 assert!(value <= MAX, "direction out of range");
113 unsafe { core::mem::transmute::<u8, Self>(value) }
115 }
116
117 #[rustfmt::skip]
119 pub(crate) const fn rotate60<const CCW: bool>(self, count: usize) -> Self {
120 use Direction::{Center, I, IJ, IK, J, JK, K};
121
122 const CCW_SEQUENCE: [Direction; 6] = [K, IK, I, IJ, J, JK];
123 const CW_SEQUENCE: [Direction; 6] = [K, JK, J, IJ, I, IK];
124
125 if count == 0 {
127 return self;
128 }
129
130 let offset = match self {
131 Center => return self,
133 K => 0,
134 J => if CCW { 4 } else { 2 },
135 JK => if CCW { 5 } else { 1 },
136 I => if CCW { 2 } else { 4 },
137 IK => if CCW { 1 } else { 5 },
138 IJ => 3,
139 };
140 let index = (count + offset) % 6;
141
142 if CCW { CCW_SEQUENCE[index] } else { CW_SEQUENCE[index] }
143 }
144
145 #[rustfmt::skip]
147 pub(crate) const fn rotate60_once<const CCW: bool>(self) -> Self {
148 use Direction::{Center, I, IJ, IK, J, JK, K};
149
150 match self {
153 Center => Center,
154 K => if CCW { IK } else { JK },
155 J => if CCW { JK } else { IJ },
156 JK => if CCW { K } else { J },
157 I => if CCW { IJ } else { IK },
158 IK => if CCW { I } else { K },
159 IJ => if CCW { J } else { I },
160 }
161 }
162
163 pub(crate) fn vertex(self, origin: CellIndex) -> Vertex {
168 let is_pentagon = origin.is_pentagon();
169
170 assert!(self != Self::Center && !(is_pentagon && self == Self::K));
173
174 let rotations = origin.vertex_rotations();
176
177 Vertex::new_unchecked(if is_pentagon {
179 let index = usize::from(self) - 2;
182 (u8::from(TO_VERTEX_PENTAGON[index]) + NUM_PENT_VERTS - rotations)
183 % NUM_PENT_VERTS
184 } else {
185 let index = usize::from(self) - 1;
187 (u8::from(TO_VERTEX_HEXAGON[index]) + NUM_HEX_VERTS - rotations)
188 % NUM_HEX_VERTS
189 })
190 }
191}
192
193impl TryFrom<u8> for Direction {
194 type Error = error::InvalidDirection;
195
196 fn try_from(value: u8) -> Result<Self, Self::Error> {
197 match value {
198 0 => Ok(Self::Center),
199 1 => Ok(Self::K),
200 2 => Ok(Self::J),
201 3 => Ok(Self::JK),
202 4 => Ok(Self::I),
203 5 => Ok(Self::IK),
204 6 => Ok(Self::IJ),
205 _ => Err(Self::Error::new(value, "out of range")),
206 }
207 }
208}
209
210impl From<Direction> for u8 {
211 fn from(value: Direction) -> Self {
212 value as Self
213 }
214}
215
216impl From<Direction> for u64 {
217 fn from(value: Direction) -> Self {
218 u8::from(value).into()
219 }
220}
221
222impl From<Direction> for usize {
223 fn from(value: Direction) -> Self {
224 u8::from(value).into()
225 }
226}
227
228impl From<Edge> for Direction {
229 fn from(value: Edge) -> Self {
230 Self::new_unchecked(value.into())
233 }
234}
235
236impl fmt::Display for Direction {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(f, "{}", u8::from(*self))
239 }
240}