geometry/
orientation.rs

1//! Utilities and types for orienting layout objects.
2
3use std::hash::Hash;
4
5use serde::{Deserialize, Serialize};
6
7use crate::transform::{Rotation, Transformation};
8
9/// A named orientation.
10#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
11#[non_exhaustive]
12pub enum NamedOrientation {
13    /// No rotations or reflections.
14    #[default]
15    R0,
16    /// Reflect vertically (ie. about the x-axis).
17    ReflectVert,
18    /// Reflect horizontally (ie. about the y-axis).
19    ReflectHoriz,
20    /// Rotate 90 degrees counter-clockwise.
21    R90,
22    /// Rotate 180 degrees counter-clockwise.
23    R180,
24    /// Rotate 270 degrees counter-clockwise.
25    R270,
26    /// Rotate 90 degrees clockwise.
27    R90Cw,
28    /// Rotate 180 degrees clockwise.
29    R180Cw,
30    /// Rotate 270 degrees clockwise.
31    R270Cw,
32    /// Flip across the line y = x.
33    FlipYx,
34    /// Flip across the line y = -x.
35    FlipMinusYx,
36}
37
38/// An orientation of a geometric object.
39///
40/// Captures reflection and rotation, but not position or scaling.
41#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
42pub struct Orientation {
43    /// Reflect vertically.
44    ///
45    /// Applied before rotation.
46    pub(crate) reflect_vert: bool,
47    /// Counter-clockwise angle in degrees.
48    ///
49    /// Applied after reflecting vertically.
50    pub(crate) angle: Rotation,
51}
52
53/// An orientation of a geometric object, represented as raw bytes.
54#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Serialize, Deserialize)]
55pub struct OrientationBytes {
56    /// Reflect vertically.
57    ///
58    /// Applied before rotation.
59    pub(crate) reflect_vert: bool,
60    /// Counter-clockwise angle in degrees, represented as raw bytes from an [`f64`].
61    ///
62    /// Applied after reflecting vertically.
63    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    /// Returns a slice of all 8 possible named rectangular orientations.
77    ///
78    /// Users should not rely upon the order of the orientations returned.
79    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    /// Converts this named orientation into a regular [`Orientation`].
93    #[inline]
94    pub fn into_orientation(self) -> Orientation {
95        Orientation::from(self)
96    }
97
98    /// Converts the given orientation to a named orientation.
99    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    /// Creates a new orientation with the given reflection and angle settings.
129    #[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    /// Returns the identity orientation with `reflect_vert = false` and `angle = 0.`.
138    pub fn identity() -> Self {
139        Self::default()
140    }
141
142    /// Applies the reflection and rotation specified in
143    /// [`Orientation`] `o` to this orientation.
144    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    /// Reflects the orientation vertically.
167    #[inline]
168    pub fn reflected_vert(self) -> Self {
169        self.apply(NamedOrientation::ReflectVert)
170    }
171
172    /// Reflects the orientation horizontally.
173    #[inline]
174    pub fn reflected_horiz(self) -> Self {
175        self.apply(NamedOrientation::ReflectHoriz)
176    }
177
178    /// Rotates the orientation 90 degrees counter-clockwise.
179    #[inline]
180    pub fn r90(self) -> Self {
181        self.apply(NamedOrientation::R90)
182    }
183
184    /// Rotates the orientation 180 degrees.
185    #[inline]
186    pub fn r180(self) -> Self {
187        self.apply(NamedOrientation::R180)
188    }
189
190    /// Rotates the orientation 180 degrees counter-clockwise.
191    #[inline]
192    pub fn r270(self) -> Self {
193        self.apply(NamedOrientation::R270)
194    }
195
196    /// Rotates the orientation 90 degrees clockwise.
197    #[inline]
198    pub fn r90cw(self) -> Self {
199        self.apply(NamedOrientation::R90Cw)
200    }
201
202    /// Rotates the orientation 180 degrees clockwise.
203    pub fn r180cw(self) -> Self {
204        self.apply(NamedOrientation::R180Cw)
205    }
206
207    /// Rotates the orientation 270 degrees clockwise.
208    #[inline]
209    pub fn r270cw(self) -> Self {
210        self.apply(NamedOrientation::R270Cw)
211    }
212
213    /// Flips the orientation around the line `y = x`.
214    #[inline]
215    pub fn flip_yx(self) -> Self {
216        self.apply(NamedOrientation::FlipYx)
217    }
218
219    /// Flips the orientation around the line `y = -x`.
220    #[inline]
221    pub fn flip_minus_yx(self) -> Self {
222        self.apply(NamedOrientation::FlipMinusYx)
223    }
224
225    /// Returns whether the orientation is reflected vertically.
226    #[inline]
227    pub fn reflect_vert(&self) -> bool {
228        self.reflect_vert
229    }
230
231    /// Returns the angle associated with this orientation.
232    #[inline]
233    pub fn angle(&self) -> Rotation {
234        self.angle
235    }
236
237    /// Returns the orientation represented by the given transformation.
238    ///
239    /// Captures the rotation and reflection encoded by the [`Transformation`],
240    /// discarding the transformation's translation.
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// # use geometry::prelude::*;
246    /// let tf = Transformation::identity();
247    /// assert_eq!(Orientation::from_transformation(tf), NamedOrientation::R0.into());
248    /// ```
249    #[inline]
250    pub fn from_transformation(value: Transformation) -> Self {
251        value.orientation()
252    }
253
254    /// Returns a slice of all 8 possible rectangular orientations.
255    ///
256    /// Users should not rely upon the order of the orientations returned.
257    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}