1use std::hash::Hash;
4
5use serde::{Deserialize, Serialize};
6
7use crate::transform::{Rotation, Transformation};
8
9#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
11#[non_exhaustive]
12pub enum NamedOrientation {
13 #[default]
15 R0,
16 ReflectVert,
18 ReflectHoriz,
20 R90,
22 R180,
24 R270,
26 R90Cw,
28 R180Cw,
30 R270Cw,
32 FlipYx,
34 FlipMinusYx,
36}
37
38#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
42pub struct Orientation {
43 pub(crate) reflect_vert: bool,
47 pub(crate) angle: Rotation,
51}
52
53#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Serialize, Deserialize)]
55pub struct OrientationBytes {
56 pub(crate) reflect_vert: bool,
60 pub(crate) angle: u64,
64}
65
66impl From<Orientation> for OrientationBytes {
67 fn from(value: Orientation) -> Self {
68 Self {
69 reflect_vert: value.reflect_vert,
70 angle: value.angle.degrees().to_bits(),
71 }
72 }
73}
74
75impl NamedOrientation {
76 pub fn all_rectangular() -> [Self; 8] {
80 [
81 Self::R0,
82 Self::ReflectVert,
83 Self::ReflectHoriz,
84 Self::R90,
85 Self::R180,
86 Self::R270,
87 Self::FlipYx,
88 Self::FlipMinusYx,
89 ]
90 }
91
92 #[inline]
94 pub fn into_orientation(self) -> Orientation {
95 Orientation::from(self)
96 }
97
98 pub fn from_orientation(orientation: Orientation) -> Self {
100 Self::all_rectangular()
101 .into_iter()
102 .find(|o| orientation == o.into_orientation())
103 .expect("orientation did not represent a valid Manhattan orientation")
104 }
105}
106
107impl From<NamedOrientation> for Orientation {
108 fn from(value: NamedOrientation) -> Self {
109 use NamedOrientation::*;
110 let (reflect_vert, angle) = match value {
111 R0 => (false, Rotation::R0),
112 R90 | R270Cw => (false, Rotation::R90),
113 R180 | R180Cw => (false, Rotation::R180),
114 R270 | R90Cw => (false, Rotation::R270),
115 ReflectVert => (true, Rotation::R0),
116 FlipYx => (true, Rotation::R90),
117 ReflectHoriz => (true, Rotation::R180),
118 FlipMinusYx => (true, Rotation::R270),
119 };
120 Self {
121 reflect_vert,
122 angle,
123 }
124 }
125}
126
127impl Orientation {
128 #[inline]
130 pub fn from_reflect_and_angle(reflect_vert: bool, angle: Rotation) -> Self {
131 Self {
132 reflect_vert,
133 angle,
134 }
135 }
136
137 pub fn identity() -> Self {
139 Self::default()
140 }
141
142 pub fn apply(mut self, o: impl Into<Orientation>) -> Self {
145 let o = o.into();
146 match (self.reflect_vert, o.reflect_vert) {
147 (false, false) => {
148 self.angle += o.angle;
149 }
150 (false, true) => {
151 self.reflect_vert = true;
152 self.angle = o.angle - self.angle;
153 }
154 (true, false) => {
155 self.angle += o.angle;
156 }
157 (true, true) => {
158 self.reflect_vert = false;
159 self.angle = o.angle - self.angle;
160 }
161 }
162
163 self
164 }
165
166 #[inline]
168 pub fn reflected_vert(self) -> Self {
169 self.apply(NamedOrientation::ReflectVert)
170 }
171
172 #[inline]
174 pub fn reflected_horiz(self) -> Self {
175 self.apply(NamedOrientation::ReflectHoriz)
176 }
177
178 #[inline]
180 pub fn r90(self) -> Self {
181 self.apply(NamedOrientation::R90)
182 }
183
184 #[inline]
186 pub fn r180(self) -> Self {
187 self.apply(NamedOrientation::R180)
188 }
189
190 #[inline]
192 pub fn r270(self) -> Self {
193 self.apply(NamedOrientation::R270)
194 }
195
196 #[inline]
198 pub fn r90cw(self) -> Self {
199 self.apply(NamedOrientation::R90Cw)
200 }
201
202 pub fn r180cw(self) -> Self {
204 self.apply(NamedOrientation::R180Cw)
205 }
206
207 #[inline]
209 pub fn r270cw(self) -> Self {
210 self.apply(NamedOrientation::R270Cw)
211 }
212
213 #[inline]
215 pub fn flip_yx(self) -> Self {
216 self.apply(NamedOrientation::FlipYx)
217 }
218
219 #[inline]
221 pub fn flip_minus_yx(self) -> Self {
222 self.apply(NamedOrientation::FlipMinusYx)
223 }
224
225 #[inline]
227 pub fn reflect_vert(&self) -> bool {
228 self.reflect_vert
229 }
230
231 #[inline]
233 pub fn angle(&self) -> Rotation {
234 self.angle
235 }
236
237 #[inline]
250 pub fn from_transformation(value: Transformation) -> Self {
251 value.orientation()
252 }
253
254 pub fn all_rectangular() -> [Self; 8] {
258 NamedOrientation::all_rectangular().map(Self::from)
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use std::collections::HashSet;
265
266 use super::*;
267
268 #[test]
269 fn orientations_convert_to_unique_bytes() {
270 let opts: [OrientationBytes; 8] = NamedOrientation::all_rectangular()
271 .map(|n| n.into_orientation())
272 .map(OrientationBytes::from);
273 let mut set = HashSet::new();
274 for item in opts {
275 set.insert(item);
276 }
277 assert_eq!(set.len(), 8);
278 }
279}