1use std::fmt;
8
9use bytes::{Bytes, BytesMut};
10
11use oxidized_codec::types::TypeError;
12use oxidized_codec::varint;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[repr(u8)]
22pub enum Direction {
23 Down = 0,
25 Up = 1,
27 North = 2,
29 South = 3,
31 West = 4,
33 East = 5,
35}
36
37pub const ALL: [Direction; 6] = [
39 Direction::Down,
40 Direction::Up,
41 Direction::North,
42 Direction::South,
43 Direction::West,
44 Direction::East,
45];
46
47pub const HORIZONTALS: [Direction; 4] = [
49 Direction::South,
50 Direction::West,
51 Direction::North,
52 Direction::East,
53];
54
55impl Direction {
56 pub fn opposite(self) -> Direction {
58 match self {
59 Direction::Down => Direction::Up,
60 Direction::Up => Direction::Down,
61 Direction::North => Direction::South,
62 Direction::South => Direction::North,
63 Direction::West => Direction::East,
64 Direction::East => Direction::West,
65 }
66 }
67
68 pub fn clockwise(self) -> Option<Direction> {
73 match self {
74 Direction::North => Some(Direction::East),
75 Direction::East => Some(Direction::South),
76 Direction::South => Some(Direction::West),
77 Direction::West => Some(Direction::North),
78 Direction::Up | Direction::Down => None,
79 }
80 }
81
82 pub fn counter_clockwise(self) -> Option<Direction> {
87 match self {
88 Direction::North => Some(Direction::West),
89 Direction::West => Some(Direction::South),
90 Direction::South => Some(Direction::East),
91 Direction::East => Some(Direction::North),
92 Direction::Up | Direction::Down => None,
93 }
94 }
95
96 pub fn step_x(self) -> i32 {
98 match self {
99 Direction::West => -1,
100 Direction::East => 1,
101 _ => 0,
102 }
103 }
104
105 pub fn step_y(self) -> i32 {
107 match self {
108 Direction::Down => -1,
109 Direction::Up => 1,
110 _ => 0,
111 }
112 }
113
114 pub fn step_z(self) -> i32 {
116 match self {
117 Direction::North => -1,
118 Direction::South => 1,
119 _ => 0,
120 }
121 }
122
123 pub fn axis(self) -> Axis {
125 match self {
126 Direction::Down | Direction::Up => Axis::Y,
127 Direction::North | Direction::South => Axis::Z,
128 Direction::West | Direction::East => Axis::X,
129 }
130 }
131
132 pub fn axis_direction(self) -> AxisDirection {
134 match self {
135 Direction::Down | Direction::North | Direction::West => AxisDirection::Negative,
136 Direction::Up | Direction::South | Direction::East => AxisDirection::Positive,
137 }
138 }
139
140 pub fn from_3d_data_value(id: u8) -> Option<Direction> {
144 match id {
145 0 => Some(Direction::Down),
146 1 => Some(Direction::Up),
147 2 => Some(Direction::North),
148 3 => Some(Direction::South),
149 4 => Some(Direction::West),
150 5 => Some(Direction::East),
151 _ => None,
152 }
153 }
154
155 pub fn from_2d_data_value(id: u8) -> Option<Direction> {
160 match id {
161 0 => Some(Direction::South),
162 1 => Some(Direction::West),
163 2 => Some(Direction::North),
164 3 => Some(Direction::East),
165 _ => None,
166 }
167 }
168
169 pub fn to_3d_data_value(self) -> u8 {
171 self as u8
172 }
173
174 pub fn to_y_rot(self) -> f32 {
179 match self {
180 Direction::South => 0.0,
181 Direction::West => 90.0,
182 Direction::North => 180.0,
183 Direction::East => 270.0,
184 Direction::Down | Direction::Up => 0.0,
185 }
186 }
187
188 pub fn from_y_rot(rot: f64) -> Direction {
192 let normalized = ((rot % 360.0) + 360.0) % 360.0;
194 let index = ((normalized + 45.0) / 90.0) as i32 & 3;
195 match index {
196 0 => Direction::South,
197 1 => Direction::West,
198 2 => Direction::North,
199 3 => Direction::East,
200 _ => Direction::South, }
202 }
203
204 pub fn name(self) -> &'static str {
206 match self {
207 Direction::Down => "down",
208 Direction::Up => "up",
209 Direction::North => "north",
210 Direction::South => "south",
211 Direction::West => "west",
212 Direction::East => "east",
213 }
214 }
215
216 pub fn is_horizontal(self) -> bool {
218 matches!(
219 self,
220 Direction::North | Direction::South | Direction::West | Direction::East
221 )
222 }
223
224 pub fn is_vertical(self) -> bool {
226 matches!(self, Direction::Up | Direction::Down)
227 }
228
229 pub fn read(buf: &mut Bytes) -> Result<Self, TypeError> {
236 let id = varint::read_varint_buf(buf)?;
237 Direction::from_3d_data_value(id as u8).ok_or(TypeError::UnexpectedEof { need: 1, have: 0 })
238 }
239
240 pub fn write(&self, buf: &mut BytesMut) {
242 varint::write_varint_buf(i32::from(self.to_3d_data_value()), buf);
243 }
244}
245
246impl fmt::Display for Direction {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.write_str(self.name())
249 }
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256pub enum Axis {
257 X,
259 Y,
261 Z,
263}
264
265impl Axis {
266 pub fn is_vertical(self) -> bool {
268 self == Axis::Y
269 }
270
271 pub fn is_horizontal(self) -> bool {
273 matches!(self, Axis::X | Axis::Z)
274 }
275
276 pub fn choose<T>(self, x: T, y: T, z: T) -> T {
278 match self {
279 Axis::X => x,
280 Axis::Y => y,
281 Axis::Z => z,
282 }
283 }
284
285 pub fn positive(self) -> Direction {
287 match self {
288 Axis::X => Direction::East,
289 Axis::Y => Direction::Up,
290 Axis::Z => Direction::South,
291 }
292 }
293
294 pub fn negative(self) -> Direction {
296 match self {
297 Axis::X => Direction::West,
298 Axis::Y => Direction::Down,
299 Axis::Z => Direction::North,
300 }
301 }
302
303 pub fn name(self) -> &'static str {
305 match self {
306 Axis::X => "x",
307 Axis::Y => "y",
308 Axis::Z => "z",
309 }
310 }
311}
312
313impl fmt::Display for Axis {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.write_str(self.name())
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
323pub enum AxisDirection {
324 Positive,
326 Negative,
328}
329
330impl AxisDirection {
331 pub fn step(self) -> i32 {
333 match self {
334 AxisDirection::Positive => 1,
335 AxisDirection::Negative => -1,
336 }
337 }
338
339 pub fn opposite(self) -> AxisDirection {
341 match self {
342 AxisDirection::Positive => AxisDirection::Negative,
343 AxisDirection::Negative => AxisDirection::Positive,
344 }
345 }
346}
347
348impl fmt::Display for AxisDirection {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 match self {
351 AxisDirection::Positive => f.write_str("positive"),
352 AxisDirection::Negative => f.write_str("negative"),
353 }
354 }
355}
356
357#[cfg(test)]
360#[allow(clippy::unwrap_used, clippy::expect_used)]
361mod tests {
362 use super::*;
363
364 #[test]
367 fn test_direction_from_3d_data_value_valid() {
368 assert_eq!(Direction::from_3d_data_value(0), Some(Direction::Down));
369 assert_eq!(Direction::from_3d_data_value(1), Some(Direction::Up));
370 assert_eq!(Direction::from_3d_data_value(2), Some(Direction::North));
371 assert_eq!(Direction::from_3d_data_value(3), Some(Direction::South));
372 assert_eq!(Direction::from_3d_data_value(4), Some(Direction::West));
373 assert_eq!(Direction::from_3d_data_value(5), Some(Direction::East));
374 }
375
376 #[test]
377 fn test_direction_from_3d_data_value_invalid() {
378 assert_eq!(Direction::from_3d_data_value(6), None);
379 assert_eq!(Direction::from_3d_data_value(255), None);
380 }
381
382 #[test]
383 fn test_direction_from_2d_data_value() {
384 assert_eq!(Direction::from_2d_data_value(0), Some(Direction::South));
385 assert_eq!(Direction::from_2d_data_value(1), Some(Direction::West));
386 assert_eq!(Direction::from_2d_data_value(2), Some(Direction::North));
387 assert_eq!(Direction::from_2d_data_value(3), Some(Direction::East));
388 assert_eq!(Direction::from_2d_data_value(4), None);
389 }
390
391 #[test]
392 fn test_direction_to_3d_data_value() {
393 for dir in ALL {
394 assert_eq!(
395 Direction::from_3d_data_value(dir.to_3d_data_value()),
396 Some(dir)
397 );
398 }
399 }
400
401 #[test]
404 fn test_direction_opposite_pairs() {
405 assert_eq!(Direction::Down.opposite(), Direction::Up);
406 assert_eq!(Direction::Up.opposite(), Direction::Down);
407 assert_eq!(Direction::North.opposite(), Direction::South);
408 assert_eq!(Direction::South.opposite(), Direction::North);
409 assert_eq!(Direction::West.opposite(), Direction::East);
410 assert_eq!(Direction::East.opposite(), Direction::West);
411 }
412
413 #[test]
414 fn test_direction_double_opposite_is_identity() {
415 for dir in ALL {
416 assert_eq!(dir.opposite().opposite(), dir);
417 }
418 }
419
420 #[test]
423 fn test_direction_clockwise_chain() {
424 let mut dir = Direction::North;
425 dir = dir.clockwise().unwrap();
426 assert_eq!(dir, Direction::East);
427 dir = dir.clockwise().unwrap();
428 assert_eq!(dir, Direction::South);
429 dir = dir.clockwise().unwrap();
430 assert_eq!(dir, Direction::West);
431 dir = dir.clockwise().unwrap();
432 assert_eq!(dir, Direction::North);
433 }
434
435 #[test]
436 fn test_direction_counter_clockwise_chain() {
437 let mut dir = Direction::North;
438 dir = dir.counter_clockwise().unwrap();
439 assert_eq!(dir, Direction::West);
440 dir = dir.counter_clockwise().unwrap();
441 assert_eq!(dir, Direction::South);
442 dir = dir.counter_clockwise().unwrap();
443 assert_eq!(dir, Direction::East);
444 dir = dir.counter_clockwise().unwrap();
445 assert_eq!(dir, Direction::North);
446 }
447
448 #[test]
449 fn test_direction_clockwise_vertical_returns_none() {
450 assert_eq!(Direction::Up.clockwise(), None);
451 assert_eq!(Direction::Down.clockwise(), None);
452 }
453
454 #[test]
455 fn test_direction_counter_clockwise_vertical_returns_none() {
456 assert_eq!(Direction::Up.counter_clockwise(), None);
457 assert_eq!(Direction::Down.counter_clockwise(), None);
458 }
459
460 #[test]
463 fn test_direction_step_vectors() {
464 assert_eq!(
465 (
466 Direction::Down.step_x(),
467 Direction::Down.step_y(),
468 Direction::Down.step_z()
469 ),
470 (0, -1, 0)
471 );
472 assert_eq!(
473 (
474 Direction::Up.step_x(),
475 Direction::Up.step_y(),
476 Direction::Up.step_z()
477 ),
478 (0, 1, 0)
479 );
480 assert_eq!(
481 (
482 Direction::North.step_x(),
483 Direction::North.step_y(),
484 Direction::North.step_z()
485 ),
486 (0, 0, -1)
487 );
488 assert_eq!(
489 (
490 Direction::South.step_x(),
491 Direction::South.step_y(),
492 Direction::South.step_z()
493 ),
494 (0, 0, 1)
495 );
496 assert_eq!(
497 (
498 Direction::West.step_x(),
499 Direction::West.step_y(),
500 Direction::West.step_z()
501 ),
502 (-1, 0, 0)
503 );
504 assert_eq!(
505 (
506 Direction::East.step_x(),
507 Direction::East.step_y(),
508 Direction::East.step_z()
509 ),
510 (1, 0, 0)
511 );
512 }
513
514 #[test]
517 fn test_direction_to_y_rot() {
518 assert_eq!(Direction::South.to_y_rot(), 0.0);
519 assert_eq!(Direction::West.to_y_rot(), 90.0);
520 assert_eq!(Direction::North.to_y_rot(), 180.0);
521 assert_eq!(Direction::East.to_y_rot(), 270.0);
522 }
523
524 #[test]
525 fn test_direction_from_y_rot_exact() {
526 assert_eq!(Direction::from_y_rot(0.0), Direction::South);
527 assert_eq!(Direction::from_y_rot(90.0), Direction::West);
528 assert_eq!(Direction::from_y_rot(180.0), Direction::North);
529 assert_eq!(Direction::from_y_rot(270.0), Direction::East);
530 }
531
532 #[test]
533 fn test_direction_from_y_rot_snapping() {
534 assert_eq!(Direction::from_y_rot(44.0), Direction::South);
535 assert_eq!(Direction::from_y_rot(46.0), Direction::West);
536 assert_eq!(Direction::from_y_rot(-90.0), Direction::East);
537 assert_eq!(Direction::from_y_rot(360.0), Direction::South);
538 assert_eq!(Direction::from_y_rot(720.0), Direction::South);
539 }
540
541 #[test]
544 fn test_direction_axis() {
545 assert_eq!(Direction::Down.axis(), Axis::Y);
546 assert_eq!(Direction::Up.axis(), Axis::Y);
547 assert_eq!(Direction::North.axis(), Axis::Z);
548 assert_eq!(Direction::South.axis(), Axis::Z);
549 assert_eq!(Direction::West.axis(), Axis::X);
550 assert_eq!(Direction::East.axis(), Axis::X);
551 }
552
553 #[test]
554 fn test_direction_axis_direction() {
555 assert_eq!(Direction::Down.axis_direction(), AxisDirection::Negative);
556 assert_eq!(Direction::Up.axis_direction(), AxisDirection::Positive);
557 assert_eq!(Direction::North.axis_direction(), AxisDirection::Negative);
558 assert_eq!(Direction::South.axis_direction(), AxisDirection::Positive);
559 assert_eq!(Direction::West.axis_direction(), AxisDirection::Negative);
560 assert_eq!(Direction::East.axis_direction(), AxisDirection::Positive);
561 }
562
563 #[test]
566 fn test_direction_is_horizontal() {
567 assert!(!Direction::Down.is_horizontal());
568 assert!(!Direction::Up.is_horizontal());
569 assert!(Direction::North.is_horizontal());
570 assert!(Direction::South.is_horizontal());
571 assert!(Direction::West.is_horizontal());
572 assert!(Direction::East.is_horizontal());
573 }
574
575 #[test]
576 fn test_direction_is_vertical() {
577 assert!(Direction::Down.is_vertical());
578 assert!(Direction::Up.is_vertical());
579 assert!(!Direction::North.is_vertical());
580 }
581
582 #[test]
585 fn test_direction_name() {
586 assert_eq!(Direction::Down.name(), "down");
587 assert_eq!(Direction::Up.name(), "up");
588 assert_eq!(Direction::North.name(), "north");
589 assert_eq!(Direction::South.name(), "south");
590 assert_eq!(Direction::West.name(), "west");
591 assert_eq!(Direction::East.name(), "east");
592 }
593
594 #[test]
595 fn test_direction_display() {
596 assert_eq!(format!("{}", Direction::North), "north");
597 }
598
599 #[test]
602 fn test_axis_is_vertical() {
603 assert!(!Axis::X.is_vertical());
604 assert!(Axis::Y.is_vertical());
605 assert!(!Axis::Z.is_vertical());
606 }
607
608 #[test]
609 fn test_axis_is_horizontal() {
610 assert!(Axis::X.is_horizontal());
611 assert!(!Axis::Y.is_horizontal());
612 assert!(Axis::Z.is_horizontal());
613 }
614
615 #[test]
616 fn test_axis_choose() {
617 assert_eq!(Axis::X.choose(10, 20, 30), 10);
618 assert_eq!(Axis::Y.choose(10, 20, 30), 20);
619 assert_eq!(Axis::Z.choose(10, 20, 30), 30);
620 }
621
622 #[test]
623 fn test_axis_positive_negative() {
624 assert_eq!(Axis::X.positive(), Direction::East);
625 assert_eq!(Axis::X.negative(), Direction::West);
626 assert_eq!(Axis::Y.positive(), Direction::Up);
627 assert_eq!(Axis::Y.negative(), Direction::Down);
628 assert_eq!(Axis::Z.positive(), Direction::South);
629 assert_eq!(Axis::Z.negative(), Direction::North);
630 }
631
632 #[test]
633 fn test_axis_name() {
634 assert_eq!(Axis::X.name(), "x");
635 assert_eq!(Axis::Y.name(), "y");
636 assert_eq!(Axis::Z.name(), "z");
637 }
638
639 #[test]
642 fn test_axis_direction_step() {
643 assert_eq!(AxisDirection::Positive.step(), 1);
644 assert_eq!(AxisDirection::Negative.step(), -1);
645 }
646
647 #[test]
648 fn test_axis_direction_opposite() {
649 assert_eq!(AxisDirection::Positive.opposite(), AxisDirection::Negative);
650 assert_eq!(AxisDirection::Negative.opposite(), AxisDirection::Positive);
651 }
652
653 #[test]
656 fn test_direction_wire_roundtrip() {
657 for dir in ALL {
658 let mut buf = BytesMut::new();
659 dir.write(&mut buf);
660 let mut data = buf.freeze();
661 let decoded = Direction::read(&mut data).unwrap();
662 assert_eq!(decoded, dir);
663 }
664 }
665
666 #[test]
669 fn test_all_contains_six_directions() {
670 assert_eq!(ALL.len(), 6);
671 }
672
673 #[test]
674 fn test_horizontals_contains_four_directions() {
675 assert_eq!(HORIZONTALS.len(), 4);
676 for dir in HORIZONTALS {
677 assert!(dir.is_horizontal());
678 }
679 }
680}