Skip to main content

oxidized_mc_types/
direction.rs

1//! [`Direction`], [`Axis`], and [`AxisDirection`] — cardinal directions in 3D space.
2//!
3//! These mirror the vanilla `Direction`, `Direction.Axis`, and
4//! `Direction.AxisDirection` enums used throughout the Minecraft protocol
5//! for block faces, entity facings, and spatial queries.
6
7use std::fmt;
8
9use bytes::{Bytes, BytesMut};
10
11use oxidized_codec::types::TypeError;
12use oxidized_codec::varint;
13
14// ── Direction ───────────────────────────────────────────────────────────
15
16/// A cardinal direction in 3D space.
17///
18/// Values 0–5 map to Down, Up, North, South, West, East and are used
19/// directly as wire IDs in many packets.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[repr(u8)]
22pub enum Direction {
23    /// Negative Y.
24    Down = 0,
25    /// Positive Y.
26    Up = 1,
27    /// Negative Z.
28    North = 2,
29    /// Positive Z.
30    South = 3,
31    /// Negative X.
32    West = 4,
33    /// Positive X.
34    East = 5,
35}
36
37/// All six directions in order of their 3D data value.
38pub const ALL: [Direction; 6] = [
39    Direction::Down,
40    Direction::Up,
41    Direction::North,
42    Direction::South,
43    Direction::West,
44    Direction::East,
45];
46
47/// The four horizontal directions: South, West, North, East (2D data-value order).
48pub const HORIZONTALS: [Direction; 4] = [
49    Direction::South,
50    Direction::West,
51    Direction::North,
52    Direction::East,
53];
54
55impl Direction {
56    /// Returns the opposite direction.
57    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    /// Rotates this direction 90° clockwise around the Y axis.
69    ///
70    /// Only valid for horizontal directions.
71    /// Returns `None` for [`Direction::Up`] and [`Direction::Down`].
72    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    /// Rotates this direction 90° counter-clockwise around the Y axis.
83    ///
84    /// Only valid for horizontal directions.
85    /// Returns `None` for [`Direction::Up`] and [`Direction::Down`].
86    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    /// X component of the direction's unit normal vector.
97    pub fn step_x(self) -> i32 {
98        match self {
99            Direction::West => -1,
100            Direction::East => 1,
101            _ => 0,
102        }
103    }
104
105    /// Y component of the direction's unit normal vector.
106    pub fn step_y(self) -> i32 {
107        match self {
108            Direction::Down => -1,
109            Direction::Up => 1,
110            _ => 0,
111        }
112    }
113
114    /// Z component of the direction's unit normal vector.
115    pub fn step_z(self) -> i32 {
116        match self {
117            Direction::North => -1,
118            Direction::South => 1,
119            _ => 0,
120        }
121    }
122
123    /// Returns the axis this direction lies on.
124    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    /// Returns the axis direction (positive or negative) for this direction.
133    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    /// Converts a 3D data value (0–5) to a [`Direction`].
141    ///
142    /// Returns `None` if `id` is out of range.
143    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    /// Converts a 2D data value to a horizontal [`Direction`].
156    ///
157    /// Mapping: 0=South, 1=West, 2=North, 3=East.
158    /// Returns `None` if `id` is out of range.
159    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    /// Returns the 3D data value (0–5) for this direction.
170    pub fn to_3d_data_value(self) -> u8 {
171        self as u8
172    }
173
174    /// Returns the Y rotation in degrees for this horizontal direction.
175    ///
176    /// South=0, West=90, North=180, East=270.
177    /// Returns 0.0 for vertical directions.
178    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    /// Converts a Y rotation (in degrees) to the nearest horizontal direction.
189    ///
190    /// South=0°, West=90°, North=180°, East=270°.
191    pub fn from_y_rot(rot: f64) -> Direction {
192        // Normalize to 0..360, then divide into 4 quadrants
193        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, // unreachable due to & 3
201        }
202    }
203
204    /// Returns the lowercase name of this direction.
205    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    /// Returns `true` if this is a horizontal direction (North/South/East/West).
217    pub fn is_horizontal(self) -> bool {
218        matches!(
219            self,
220            Direction::North | Direction::South | Direction::West | Direction::East
221        )
222    }
223
224    /// Returns `true` if this is a vertical direction (Up/Down).
225    pub fn is_vertical(self) -> bool {
226        matches!(self, Direction::Up | Direction::Down)
227    }
228
229    /// Reads a [`Direction`] from a wire buffer as a VarInt (0–5).
230    ///
231    /// # Errors
232    ///
233    /// Returns [`TypeError`] if the buffer is truncated or the value is
234    /// out of range.
235    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    /// Writes this [`Direction`] to a wire buffer as a VarInt.
241    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// ── Axis ────────────────────────────────────────────────────────────────
253
254/// A coordinate axis in 3D space.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256pub enum Axis {
257    /// The X axis (east/west).
258    X,
259    /// The Y axis (up/down).
260    Y,
261    /// The Z axis (north/south).
262    Z,
263}
264
265impl Axis {
266    /// Returns `true` if this is the Y axis.
267    pub fn is_vertical(self) -> bool {
268        self == Axis::Y
269    }
270
271    /// Returns `true` if this is the X or Z axis.
272    pub fn is_horizontal(self) -> bool {
273        matches!(self, Axis::X | Axis::Z)
274    }
275
276    /// Selects the component corresponding to this axis.
277    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    /// Returns the direction along the positive end of this axis.
286    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    /// Returns the direction along the negative end of this axis.
295    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    /// Returns the lowercase name of this axis.
304    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// ── AxisDirection ───────────────────────────────────────────────────────
320
321/// Direction along an axis (positive or negative).
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
323pub enum AxisDirection {
324    /// The positive direction (+1).
325    Positive,
326    /// The negative direction (−1).
327    Negative,
328}
329
330impl AxisDirection {
331    /// Returns the integer step for this direction (+1 or −1).
332    pub fn step(self) -> i32 {
333        match self {
334            AxisDirection::Positive => 1,
335            AxisDirection::Negative => -1,
336        }
337    }
338
339    /// Returns the opposite axis direction.
340    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// ── Tests ───────────────────────────────────────────────────────────────
358
359#[cfg(test)]
360#[allow(clippy::unwrap_used, clippy::expect_used)]
361mod tests {
362    use super::*;
363
364    // ── Construction ─────────────────────────────────────────────────
365
366    #[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    // ── Opposite ─────────────────────────────────────────────────────
402
403    #[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    // ── Clockwise rotation ──────────────────────────────────────────
421
422    #[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    // ── Step vectors ────────────────────────────────────────────────
461
462    #[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    // ── Y rotation ──────────────────────────────────────────────────
515
516    #[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    // ── Axis ────────────────────────────────────────────────────────
542
543    #[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    // ── Horizontal / Vertical ───────────────────────────────────────
564
565    #[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    // ── Name / Display ──────────────────────────────────────────────
583
584    #[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    // ── Axis type ───────────────────────────────────────────────────
600
601    #[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    // ── AxisDirection type ──────────────────────────────────────────
640
641    #[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    // ── Wire roundtrip ──────────────────────────────────────────────
654
655    #[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    // ── ALL / HORIZONTALS ───────────────────────────────────────────
667
668    #[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}