1use crate::random::Random;
4use crate::{BoundingBox, Direction};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Rotation {
9 None,
11 Clockwise90,
13 Clockwise180,
15 CounterClockwise90,
17}
18
19const ALL_ROTATIONS: [Rotation; 4] = [
20 Rotation::None,
21 Rotation::Clockwise90,
22 Rotation::Clockwise180,
23 Rotation::CounterClockwise90,
24];
25
26impl Rotation {
27 #[must_use]
29 pub fn get_random(rng: &mut impl Random) -> Self {
30 ALL_ROTATIONS[rng.next_i32_bounded(4) as usize]
31 }
32
33 #[must_use]
35 pub fn get_shuffled(rng: &mut impl Random) -> [Rotation; 4] {
36 let mut rotations = ALL_ROTATIONS;
37 for i in (1..4).rev() {
38 let j = rng.next_i32_bounded((i + 1) as i32) as usize;
39 rotations.swap(i, j);
40 }
41 rotations
42 }
43
44 #[must_use]
46 pub const fn rotate(self, dir: Direction) -> Direction {
47 match self {
48 Self::None => dir,
49 Self::Clockwise90 => dir.rotate_y_clockwise(),
50 Self::Clockwise180 => dir.rotate_y_clockwise().rotate_y_clockwise(),
51 Self::CounterClockwise90 => dir.rotate_y_counter_clockwise(),
52 }
53 }
54
55 #[must_use]
57 pub const fn then(self, other: Self) -> Self {
58 ALL_ROTATIONS[((self as u8 + other as u8) % 4) as usize]
59 }
60
61 #[must_use]
63 pub const fn transform_pos(
64 self,
65 x: i32,
66 y: i32,
67 z: i32,
68 pivot_x: i32,
69 pivot_z: i32,
70 ) -> (i32, i32, i32) {
71 match self {
72 Self::None => (x, y, z),
73 Self::Clockwise90 => (pivot_x + pivot_z - z, y, pivot_z - pivot_x + x),
74 Self::Clockwise180 => (pivot_x + pivot_x - x, y, pivot_z + pivot_z - z),
75 Self::CounterClockwise90 => (pivot_x - pivot_z + z, y, pivot_x + pivot_z - x),
76 }
77 }
78
79 #[must_use]
81 pub const fn rotate_size(self, size_x: i32, size_y: i32, size_z: i32) -> (i32, i32, i32) {
82 match self {
83 Self::Clockwise90 | Self::CounterClockwise90 => (size_z, size_y, size_x),
84 Self::None | Self::Clockwise180 => (size_x, size_y, size_z),
85 }
86 }
87
88 #[must_use]
90 pub const fn transform_pos_mirrored(
91 self,
92 x: i32,
93 y: i32,
94 z: i32,
95 pivot_x: i32,
96 pivot_z: i32,
97 mirror_front_back: bool,
98 ) -> (i32, i32, i32) {
99 let mx = if mirror_front_back { -x } else { x };
100 self.transform_pos(mx, y, z, pivot_x, pivot_z)
101 }
102
103 #[must_use]
105 pub const fn get_bounding_box_full(
106 self,
107 pos: (i32, i32, i32),
108 size: (i32, i32, i32),
109 pivot_x: i32,
110 pivot_z: i32,
111 mirror_front_back: bool,
112 ) -> BoundingBox {
113 let (c1x, c1y, c1z) =
114 self.transform_pos_mirrored(0, 0, 0, pivot_x, pivot_z, mirror_front_back);
115 let (c2x, c2y, c2z) = self.transform_pos_mirrored(
116 size.0 - 1,
117 size.1 - 1,
118 size.2 - 1,
119 pivot_x,
120 pivot_z,
121 mirror_front_back,
122 );
123 BoundingBox::new(
124 c1x.min(c2x) + pos.0,
125 c1y.min(c2y) + pos.1,
126 c1z.min(c2z) + pos.2,
127 c1x.max(c2x) + pos.0,
128 c1y.max(c2y) + pos.1,
129 c1z.max(c2z) + pos.2,
130 )
131 }
132
133 #[must_use]
135 pub const fn get_bounding_box_with_pivot(
136 self,
137 pos: (i32, i32, i32),
138 size: (i32, i32, i32),
139 pivot_x: i32,
140 pivot_z: i32,
141 ) -> BoundingBox {
142 self.get_bounding_box_full(pos, size, pivot_x, pivot_z, false)
143 }
144
145 #[must_use]
147 pub const fn get_bounding_box(
148 self,
149 pos_x: i32,
150 pos_y: i32,
151 pos_z: i32,
152 size_x: i32,
153 size_y: i32,
154 size_z: i32,
155 ) -> BoundingBox {
156 self.get_bounding_box_full((pos_x, pos_y, pos_z), (size_x, size_y, size_z), 0, 0, false)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn rotate_direction() {
166 assert_eq!(Rotation::None.rotate(Direction::North), Direction::North);
167 assert_eq!(
168 Rotation::Clockwise90.rotate(Direction::North),
169 Direction::East
170 );
171 assert_eq!(
172 Rotation::Clockwise180.rotate(Direction::North),
173 Direction::South
174 );
175 assert_eq!(
176 Rotation::CounterClockwise90.rotate(Direction::North),
177 Direction::West
178 );
179 }
180
181 #[test]
182 fn compose_rotations() {
183 assert_eq!(
184 Rotation::Clockwise90.then(Rotation::Clockwise90),
185 Rotation::Clockwise180
186 );
187 assert_eq!(
188 Rotation::Clockwise90.then(Rotation::CounterClockwise90),
189 Rotation::None
190 );
191 assert_eq!(
192 Rotation::Clockwise180.then(Rotation::Clockwise180),
193 Rotation::None
194 );
195 }
196
197 #[test]
198 fn vertical_unchanged() {
199 assert_eq!(Rotation::Clockwise90.rotate(Direction::Up), Direction::Up);
200 assert_eq!(
201 Rotation::Clockwise180.rotate(Direction::Down),
202 Direction::Down
203 );
204 }
205
206 #[test]
207 fn transform_pos_pivot_zero() {
208 assert_eq!(Rotation::None.transform_pos(3, 5, 7, 0, 0), (3, 5, 7));
209 assert_eq!(
210 Rotation::Clockwise90.transform_pos(3, 5, 7, 0, 0),
211 (-7, 5, 3)
212 );
213 assert_eq!(
214 Rotation::Clockwise180.transform_pos(3, 5, 7, 0, 0),
215 (-3, 5, -7)
216 );
217 assert_eq!(
218 Rotation::CounterClockwise90.transform_pos(3, 5, 7, 0, 0),
219 (7, 5, -3)
220 );
221 }
222
223 #[test]
224 fn bounding_box_none() {
225 let bb = Rotation::None.get_bounding_box(0, 0, 0, 6, 10, 6);
226 assert_eq!((bb.min_x, bb.min_y, bb.min_z), (0, 0, 0));
227 assert_eq!((bb.max_x, bb.max_y, bb.max_z), (5, 9, 5));
228 }
229
230 #[test]
231 fn bounding_box_cw90() {
232 let bb = Rotation::Clockwise90.get_bounding_box(100, 50, 200, 6, 10, 8);
233 assert_eq!((bb.min_x, bb.min_y, bb.min_z), (93, 50, 200));
234 assert_eq!((bb.max_x, bb.max_y, bb.max_z), (100, 59, 205));
235 }
236
237 #[test]
238 fn bounding_box_cw180() {
239 let bb = Rotation::Clockwise180.get_bounding_box(0, 0, 0, 6, 10, 8);
240 assert_eq!((bb.min_x, bb.min_y, bb.min_z), (-5, 0, -7));
241 assert_eq!((bb.max_x, bb.max_y, bb.max_z), (0, 9, 0));
242 }
243
244 #[test]
245 fn rotate_size() {
246 assert_eq!(Rotation::None.rotate_size(6, 10, 8), (6, 10, 8));
247 assert_eq!(Rotation::Clockwise90.rotate_size(6, 10, 8), (8, 10, 6));
248 assert_eq!(Rotation::Clockwise180.rotate_size(6, 10, 8), (6, 10, 8));
249 assert_eq!(
250 Rotation::CounterClockwise90.rotate_size(6, 10, 8),
251 (8, 10, 6)
252 );
253 }
254}