1use std::io::{self, Cursor};
6
7use glam::IVec3;
8use steel_math::{DEGREE_90, DEGREE_180, DEGREE_270};
9
10use crate::{axis::Axis, codec::VarInt, serial::ReadFrom, types::BlockPos};
11
12#[derive(Clone, Copy, Debug)]
14#[derive_const(PartialEq)]
15pub enum Direction {
16 Down,
18 Up,
20 North,
22 South,
24 West,
26 East,
28}
29
30impl ReadFrom for Direction {
31 fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
32 let id = VarInt::read(data)?.0;
33 if (0..6).contains(&id) {
34 Ok(Direction::from_3d_data_value(id))
35 } else {
36 Err(io::Error::other("Invalid Direction id"))
37 }
38 }
39}
40
41impl Direction {
42 #[must_use]
44 pub const fn get_3d_data_value(self) -> i32 {
45 match self {
46 Direction::Down => 0,
47 Direction::Up => 1,
48 Direction::North => 2,
49 Direction::South => 3,
50 Direction::West => 4,
51 Direction::East => 5,
52 }
53 }
54
55 #[must_use]
59 pub const fn from_3d_data_value(data: i32) -> Direction {
60 let rem = data % 6;
61 let idx = if rem < 0 {
62 (-rem) as usize
63 } else {
64 rem as usize
65 };
66 Direction::ALL[idx]
67 }
68
69 #[must_use]
71 pub const fn offset(self) -> (i32, i32, i32) {
72 match self {
73 Direction::Down => (0, -1, 0),
74 Direction::Up => (0, 1, 0),
75 Direction::North => (0, 0, -1),
76 Direction::South => (0, 0, 1),
77 Direction::West => (-1, 0, 0),
78 Direction::East => (1, 0, 0),
79 }
80 }
81
82 #[must_use]
86 pub const fn offset_xz(self) -> (i32, i32) {
87 match self {
88 Direction::Down | Direction::Up => (0, 0),
89 Direction::North => (0, -1),
90 Direction::South => (0, 1),
91 Direction::West => (-1, 0),
92 Direction::East => (1, 0),
93 }
94 }
95
96 #[must_use]
98 pub const fn offset_vec(self) -> IVec3 {
99 let (x, y, z) = self.offset();
100 IVec3::new(x, y, z)
101 }
102
103 #[must_use]
105 pub fn relative(self, pos: BlockPos) -> BlockPos {
106 BlockPos(pos.0 + self.offset_vec())
107 }
108
109 #[must_use]
111 pub const fn get_axis(self) -> Axis {
112 match self {
113 Direction::Down | Direction::Up => Axis::Y,
114 Direction::North | Direction::South => Axis::Z,
115 Direction::West | Direction::East => Axis::X,
116 }
117 }
118
119 #[must_use]
121 pub const fn opposite(self) -> Direction {
122 match self {
123 Direction::Down => Direction::Up,
124 Direction::Up => Direction::Down,
125 Direction::North => Direction::South,
126 Direction::South => Direction::North,
127 Direction::West => Direction::East,
128 Direction::East => Direction::West,
129 }
130 }
131
132 #[must_use]
134 pub fn from_yaw(yaw: f32) -> Direction {
135 match ((f64::from(yaw) / f64::from(DEGREE_90) + 0.5).floor() as i32) & 3 {
136 0 => Direction::South,
137 1 => Direction::West,
138 2 => Direction::North,
139 _ => Direction::East,
140 }
141 }
142
143 #[must_use]
148 pub const fn to_yaw(self) -> f32 {
149 match self {
150 Direction::North => DEGREE_180,
151 Direction::South | Direction::Up | Direction::Down => 0.0,
152 Direction::West => DEGREE_90,
153 Direction::East => DEGREE_270,
154 }
155 }
156
157 #[must_use]
159 pub const fn axis(self) -> Axis {
160 self.get_axis()
161 }
162
163 #[must_use]
165 pub const fn positive_for_axis(axis: Axis) -> Direction {
166 match axis {
167 Axis::X => Direction::East,
168 Axis::Y => Direction::Up,
169 Axis::Z => Direction::South,
170 }
171 }
172
173 #[must_use]
175 pub const fn is_horizontal(self) -> bool {
176 matches!(
177 self,
178 Direction::North | Direction::South | Direction::East | Direction::West
179 )
180 }
181
182 #[must_use]
186 pub const fn rotate_y_clockwise(self) -> Direction {
187 match self {
188 Direction::North => Direction::East,
189 Direction::East => Direction::South,
190 Direction::South => Direction::West,
191 Direction::West => Direction::North,
192 other => other,
193 }
194 }
195
196 #[must_use]
200 pub const fn rotate_y_counter_clockwise(self) -> Direction {
201 match self {
202 Direction::North => Direction::West,
203 Direction::West => Direction::South,
204 Direction::South => Direction::East,
205 Direction::East => Direction::North,
206 other => other,
207 }
208 }
209
210 pub const UPDATE_SHAPE_ORDER: [Direction; 6] = [
213 Direction::West,
214 Direction::East,
215 Direction::North,
216 Direction::South,
217 Direction::Down,
218 Direction::Up,
219 ];
220
221 pub const FLOW_NEIGHBOR_CHECK: [Direction; 5] = [
224 Direction::Up,
225 Direction::North,
226 Direction::South,
227 Direction::West,
228 Direction::East,
229 ];
230
231 pub const HORIZONTAL: [Direction; 4] = [
233 Direction::North,
234 Direction::East,
235 Direction::South,
236 Direction::West,
237 ];
238
239 pub const ALL: [Direction; 6] = [
241 Direction::Down,
242 Direction::Up,
243 Direction::North,
244 Direction::South,
245 Direction::West,
246 Direction::East,
247 ];
248
249 #[must_use]
255 pub fn ordered_by_nearest(yaw: f32, pitch: f32) -> [Direction; 6] {
256 let pitch_rad = pitch.to_radians();
258 let yaw_rad = (-yaw).to_radians();
259
260 let pitch_sin = pitch_rad.sin();
261 let pitch_cos = pitch_rad.cos();
262 let yaw_sin = yaw_rad.sin();
263 let yaw_cos = yaw_rad.cos();
264
265 let x_pos = yaw_sin > 0.0;
267 let y_pos = pitch_sin < 0.0; let z_pos = yaw_cos > 0.0;
269
270 let x_yaw = if x_pos { yaw_sin } else { -yaw_sin };
272 let y_mag = if y_pos { -pitch_sin } else { pitch_sin };
273 let z_yaw = if z_pos { yaw_cos } else { -yaw_cos };
274 let x_mag = x_yaw * pitch_cos;
275 let z_mag = z_yaw * pitch_cos;
276
277 let axis_x = if x_pos {
279 Direction::East
280 } else {
281 Direction::West
282 };
283 let axis_y = if y_pos {
284 Direction::Up
285 } else {
286 Direction::Down
287 };
288 let axis_z = if z_pos {
289 Direction::South
290 } else {
291 Direction::North
292 };
293
294 if x_yaw > z_yaw {
296 if y_mag > x_mag {
297 Self::make_direction_array(axis_y, axis_x, axis_z)
298 } else if z_mag > y_mag {
299 Self::make_direction_array(axis_x, axis_z, axis_y)
300 } else {
301 Self::make_direction_array(axis_x, axis_y, axis_z)
302 }
303 } else if y_mag > z_mag {
304 Self::make_direction_array(axis_y, axis_z, axis_x)
305 } else if x_mag > y_mag {
306 Self::make_direction_array(axis_z, axis_x, axis_y)
307 } else {
308 Self::make_direction_array(axis_z, axis_y, axis_x)
309 }
310 }
311
312 const fn make_direction_array(
317 axis1: Direction,
318 axis2: Direction,
319 axis3: Direction,
320 ) -> [Direction; 6] {
321 [
322 axis1,
323 axis2,
324 axis3,
325 axis3.opposite(),
326 axis2.opposite(),
327 axis1.opposite(),
328 ]
329 }
330
331 #[must_use]
333 pub const fn as_str(&self) -> &str {
334 match self {
335 Direction::Down => "down",
336 Direction::Up => "up",
337 Direction::North => "north",
338 Direction::South => "south",
339 Direction::West => "west",
340 Direction::East => "east",
341 }
342 }
343
344 #[must_use]
346 pub fn is_facing_yaw(self, yaw: f32) -> bool {
347 let radians = yaw.to_radians();
348 let dx = -radians.sin();
349 let dz = radians.cos();
350
351 let (nx, nz) = self.offset_xz();
352
353 (nx as f32) * dx + (nz as f32) * dz > 0.0
354 }
355
356 #[must_use]
358 pub fn random() -> Self {
359 match rand::random_range(0..6) {
360 1 => Self::Up,
361 2 => Self::North,
362 3 => Self::South,
363 4 => Self::West,
364 5 => Self::East,
365 _ => Self::Down,
366 }
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use crate::axis::Axis;
373
374 use super::Direction;
375
376 #[test]
377 fn horizontal_dirs_matches_vanilla_plane_horizontal_order() {
378 assert_eq!(
379 Direction::HORIZONTAL,
380 [
381 Direction::North,
382 Direction::East,
383 Direction::South,
384 Direction::West,
385 ]
386 );
387 }
388
389 #[test]
390 fn all_dirs_matches_vanilla_values_order() {
391 assert_eq!(
392 Direction::ALL,
393 [
394 Direction::Down,
395 Direction::Up,
396 Direction::North,
397 Direction::South,
398 Direction::West,
399 Direction::East,
400 ]
401 );
402 }
403
404 #[test]
405 fn three_d_data_value_round_trips_vanilla_ids() {
406 for direction in Direction::ALL {
407 let id = direction.get_3d_data_value();
408 assert_eq!(Direction::from_3d_data_value(id), direction);
409 }
410 }
411
412 #[test]
413 fn from_yaw_matches_vanilla_from_y_rot_boundaries() {
414 let cases = [
415 (0.0, Direction::South),
416 (44.999, Direction::South),
417 (45.0, Direction::West),
418 (134.999, Direction::West),
419 (135.0, Direction::North),
420 (224.999, Direction::North),
421 (225.0, Direction::East),
422 (314.999, Direction::East),
423 (315.0, Direction::South),
424 (-44.999, Direction::South),
425 (-45.0, Direction::South),
426 (-45.001, Direction::East),
427 (-135.0, Direction::East),
428 (-225.0, Direction::North),
429 (-315.0, Direction::West),
430 (405.0, Direction::West),
431 ];
432
433 for (yaw, direction) in cases {
434 assert_eq!(Direction::from_yaw(yaw), direction, "yaw {yaw}");
435 }
436 }
437
438 #[test]
439 fn from_3d_data_value_matches_java_abs_mod_wrap() {
440 assert_eq!(Direction::from_3d_data_value(6), Direction::Down);
442 assert_eq!(Direction::from_3d_data_value(7), Direction::Up);
443 assert_eq!(Direction::from_3d_data_value(-1), Direction::Up);
444 assert_eq!(Direction::from_3d_data_value(-7), Direction::Up);
445 assert_eq!(Direction::from_3d_data_value(-6), Direction::Down);
446 }
447
448 #[test]
449 fn from_yaw_preserves_vanilla_double_precision_for_large_angles() {
450 assert_eq!(Direction::from_yaw(193_273_528_320.0), Direction::East);
452 }
453
454 #[test]
455 fn positive_for_axis_matches_vanilla_axis_direction_positive() {
456 assert_eq!(Direction::positive_for_axis(Axis::X), Direction::East);
457 assert_eq!(Direction::positive_for_axis(Axis::Y), Direction::Up);
458 assert_eq!(Direction::positive_for_axis(Axis::Z), Direction::South);
459 }
460}