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