Skip to main content

steel_utils/
direction.rs

1//! Direction enum representing the six cardinal directions in Minecraft.
2//!
3//! This is moved from `steel-registry::blocks::properties::Direction`.
4
5use std::io::{self, Cursor};
6
7use crate::{axis::Axis, codec::VarInt, serial::ReadFrom, types::BlockPos};
8
9/// The six cardinal directions in Minecraft.
10#[derive(Clone, Copy, Debug)]
11#[derive_const(PartialEq)]
12pub enum Direction {
13    /// Negative Y direction.
14    Down,
15    /// Positive Y direction.
16    Up,
17    /// Negative Z direction.
18    North,
19    /// Positive Z direction.
20    South,
21    /// Negative X direction.
22    West,
23    /// Positive X direction.
24    East,
25}
26
27impl ReadFrom for Direction {
28    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
29        let id = VarInt::read(data)?.0;
30        match id {
31            0 => Ok(Direction::Down),
32            1 => Ok(Direction::Up),
33            2 => Ok(Direction::North),
34            3 => Ok(Direction::South),
35            4 => Ok(Direction::West),
36            5 => Ok(Direction::East),
37            _ => Err(io::Error::other("Invalid Direction id")),
38        }
39    }
40}
41
42impl Direction {
43    /// Returns the block position offset for this direction.
44    #[must_use]
45    pub const fn offset(self) -> (i32, i32, i32) {
46        match self {
47            Direction::Down => (0, -1, 0),
48            Direction::Up => (0, 1, 0),
49            Direction::North => (0, 0, -1),
50            Direction::South => (0, 0, 1),
51            Direction::West => (-1, 0, 0),
52            Direction::East => (1, 0, 0),
53        }
54    }
55
56    /// Returns the block position relative to the given position in this direction.
57    #[must_use]
58    pub const fn relative(self, pos: BlockPos) -> BlockPos {
59        let (dx, dy, dz) = self.offset();
60        pos.offset(dx, dy, dz)
61    }
62
63    /// Returns the axis this direction is on.
64    #[must_use]
65    pub const fn get_axis(self) -> Axis {
66        match self {
67            Direction::Down | Direction::Up => Axis::Y,
68            Direction::North | Direction::South => Axis::Z,
69            Direction::West | Direction::East => Axis::X,
70        }
71    }
72
73    /// Returns the opposite direction.
74    #[must_use]
75    pub const fn opposite(self) -> Direction {
76        match self {
77            Direction::Down => Direction::Up,
78            Direction::Up => Direction::Down,
79            Direction::North => Direction::South,
80            Direction::South => Direction::North,
81            Direction::West => Direction::East,
82            Direction::East => Direction::West,
83        }
84    }
85
86    /// Returns the horizontal direction from a yaw rotation.
87    ///
88    /// Yaw values follow Minecraft's convention:
89    /// - 0° = South (+Z)
90    /// - 90° = West (-X)
91    /// - 180° = North (-Z)
92    /// - 270° = East (+X)
93    #[must_use]
94    pub fn from_yaw(yaw: f32) -> Direction {
95        let adjusted = yaw.rem_euclid(360.0);
96        match adjusted {
97            y if !(45.0..315.0).contains(&y) => Direction::South,
98            y if y < 135.0 => Direction::West,
99            y if y < 225.0 => Direction::North,
100            _ => Direction::East,
101        }
102    }
103
104    /// Returns the yaw rotation for this direction.
105    ///
106    /// Only meaningful for horizontal directions.
107    /// Vertical directions return 0.
108    #[must_use]
109    pub const fn to_yaw(self) -> f32 {
110        match self {
111            Direction::North => 180.0,
112            Direction::South | Direction::Up | Direction::Down => 0.0,
113            Direction::West => 90.0,
114            Direction::East => 270.0,
115        }
116    }
117
118    /// Returns the axis this direction is on.
119    #[must_use]
120    pub const fn axis(self) -> Axis {
121        self.get_axis()
122    }
123
124    /// Returns whether this direction is horizontal (not up or down).
125    #[must_use]
126    pub const fn is_horizontal(self) -> bool {
127        matches!(
128            self,
129            Direction::North | Direction::South | Direction::East | Direction::West
130        )
131    }
132
133    /// Rotates this direction 90 degrees clockwise around the Y axis.
134    ///
135    /// Vertical directions are unchanged.
136    #[must_use]
137    pub const fn rotate_y_clockwise(self) -> Direction {
138        match self {
139            Direction::North => Direction::East,
140            Direction::East => Direction::South,
141            Direction::South => Direction::West,
142            Direction::West => Direction::North,
143            other => other,
144        }
145    }
146
147    /// Rotates this direction 90 degrees counter-clockwise around the Y axis.
148    ///
149    /// Vertical directions are unchanged.
150    #[must_use]
151    pub const fn rotate_y_counter_clockwise(self) -> Direction {
152        match self {
153            Direction::North => Direction::West,
154            Direction::West => Direction::South,
155            Direction::South => Direction::East,
156            Direction::East => Direction::North,
157            other => other,
158        }
159    }
160
161    /// The order in which neighbor shape updates are processed.
162    /// This matches vanilla's `BlockBehavior.UPDATE_SHAPE_ORDER`.
163    pub const UPDATE_SHAPE_ORDER: [Direction; 6] = [
164        Direction::West,
165        Direction::East,
166        Direction::North,
167        Direction::South,
168        Direction::Down,
169        Direction::Up,
170    ];
171
172    /// Vanilla: `LiquidBlock.POSSIBLE_FLOW_DIRECTIONS` mapped through `getOpposite()`.
173    /// Used by `LiquidBlock.shouldSpreadLiquid()` to check neighbors for lava-water interactions.
174    pub const FLOW_NEIGHBOR_CHECK: [Direction; 5] = [
175        Direction::Up,
176        Direction::North,
177        Direction::South,
178        Direction::West,
179        Direction::East,
180    ];
181
182    /// The 4 horizontal directions.
183    pub const HORIZONTAL: [Direction; 4] = [
184        Direction::North,
185        Direction::South,
186        Direction::West,
187        Direction::East,
188    ];
189
190    /// The 6 directions.
191    pub const ALL: [Direction; 6] = [
192        Direction::North,
193        Direction::South,
194        Direction::West,
195        Direction::East,
196        Direction::Down,
197        Direction::Up,
198    ];
199
200    /// Returns all directions ordered by how closely they match the player's look direction.
201    ///
202    /// This matches vanilla's `Direction.orderedByNearest(Entity)`.
203    /// - `yaw`: Player's yaw rotation in degrees (0 = South, 90 = West, 180 = North, 270 = East)
204    /// - `pitch`: Player's pitch rotation in degrees (negative = looking up, positive = looking down)
205    #[must_use]
206    pub fn ordered_by_nearest(yaw: f32, pitch: f32) -> [Direction; 6] {
207        // Convert to radians and negate yaw to match vanilla's coordinate system
208        let pitch_rad = pitch.to_radians();
209        let yaw_rad = (-yaw).to_radians();
210
211        let pitch_sin = pitch_rad.sin();
212        let pitch_cos = pitch_rad.cos();
213        let yaw_sin = yaw_rad.sin();
214        let yaw_cos = yaw_rad.cos();
215
216        // Determine which direction on each axis the player is looking
217        let x_pos = yaw_sin > 0.0;
218        let y_pos = pitch_sin < 0.0; // Negative pitch = looking up
219        let z_pos = yaw_cos > 0.0;
220
221        // Calculate magnitude of look direction on each axis
222        let x_yaw = if x_pos { yaw_sin } else { -yaw_sin };
223        let y_mag = if y_pos { -pitch_sin } else { pitch_sin };
224        let z_yaw = if z_pos { yaw_cos } else { -yaw_cos };
225        let x_mag = x_yaw * pitch_cos;
226        let z_mag = z_yaw * pitch_cos;
227
228        // Determine the primary direction on each axis
229        let axis_x = if x_pos {
230            Direction::East
231        } else {
232            Direction::West
233        };
234        let axis_y = if y_pos {
235            Direction::Up
236        } else {
237            Direction::Down
238        };
239        let axis_z = if z_pos {
240            Direction::South
241        } else {
242            Direction::North
243        };
244
245        // Sort axes by magnitude and build the direction array
246        if x_yaw > z_yaw {
247            if y_mag > x_mag {
248                Self::make_direction_array(axis_y, axis_x, axis_z)
249            } else if z_mag > y_mag {
250                Self::make_direction_array(axis_x, axis_z, axis_y)
251            } else {
252                Self::make_direction_array(axis_x, axis_y, axis_z)
253            }
254        } else if y_mag > z_mag {
255            Self::make_direction_array(axis_y, axis_z, axis_x)
256        } else if x_mag > y_mag {
257            Self::make_direction_array(axis_z, axis_x, axis_y)
258        } else {
259            Self::make_direction_array(axis_z, axis_y, axis_x)
260        }
261    }
262
263    /// Creates an array of all 6 directions ordered by magnitude.
264    ///
265    /// The order is: 3 primary directions by magnitude, then their opposites in reverse order.
266    /// This matches vanilla's `Direction.makeDirectionArray()`.
267    const fn make_direction_array(
268        axis1: Direction,
269        axis2: Direction,
270        axis3: Direction,
271    ) -> [Direction; 6] {
272        [
273            axis1,
274            axis2,
275            axis3,
276            axis3.opposite(),
277            axis2.opposite(),
278            axis1.opposite(),
279        ]
280    }
281
282    /// Returns the direction name as a string (for `PropertyEnum` compatibility).
283    #[must_use]
284    pub const fn as_str(&self) -> &str {
285        match self {
286            Direction::Down => "down",
287            Direction::Up => "up",
288            Direction::North => "north",
289            Direction::South => "south",
290            Direction::West => "west",
291            Direction::East => "east",
292        }
293    }
294}