chunkedge_protocol/
block_pos.rs

1use std::fmt;
2use std::io::Write;
3use std::ops::{Add, Sub};
4
5use anyhow::bail;
6use bitfield_struct::bitfield;
7use chunkedge_binary::{Decode, Encode};
8use chunkedge_math::{DVec3, IVec3};
9use derive_more::From;
10use thiserror::Error;
11
12use crate::direction::Direction;
13
14/// Represents an absolute block position in world space.
15#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
16pub struct BlockPos {
17    pub x: i32,
18    pub y: i32,
19    pub z: i32,
20}
21
22impl BlockPos {
23    /// Constructs a new block position.
24    pub const fn new(x: i32, y: i32, z: i32) -> Self {
25        Self { x, y, z }
26    }
27
28    /// Get a new [`BlockPos`] that is adjacent to this position in `dir`
29    /// direction.
30    ///
31    /// ```
32    /// use chunkedge_protocol::{BlockPos, Direction};
33    ///
34    /// let pos = BlockPos::new(0, 0, 0);
35    /// let adj = pos.get_in_direction(Direction::South);
36    /// assert_eq!(adj, BlockPos::new(0, 0, 1));
37    /// ```
38    pub const fn get_in_direction(self, dir: Direction) -> Self {
39        match dir {
40            Direction::Down => BlockPos::new(self.x, self.y - 1, self.z),
41            Direction::Up => BlockPos::new(self.x, self.y + 1, self.z),
42            Direction::North => BlockPos::new(self.x, self.y, self.z - 1),
43            Direction::South => BlockPos::new(self.x, self.y, self.z + 1),
44            Direction::West => BlockPos::new(self.x - 1, self.y, self.z),
45            Direction::East => BlockPos::new(self.x + 1, self.y, self.z),
46        }
47    }
48
49    /// Get the center of a block position by adding 0.5 to each axis.
50    pub const fn to_center_dvec3(self) -> DVec3 {
51        DVec3::new(
52            self.x as f64 + 0.5,
53            self.y as f64 + 0.5,
54            self.z as f64 + 0.5,
55        )
56    }
57
58    /// Get the bottom center of a block position by adding 0.5 to the x and z
59    /// axes.
60    pub const fn to_bottom_center_dvec3(self) -> DVec3 {
61        DVec3::new(self.x as f64 + 0.5, self.y as f64, self.z as f64 + 0.5)
62    }
63
64    /// Convert the block position to a vector without centering.
65    pub const fn to_dvec3(self) -> DVec3 {
66        DVec3::new(self.x as f64, self.y as f64, self.z as f64)
67    }
68
69    pub const fn offset(self, x: i32, y: i32, z: i32) -> Self {
70        Self::new(self.x + x, self.y + y, self.z + z)
71    }
72
73    pub const fn packed(self) -> Result<PackedBlockPos, Error> {
74        match (self.x, self.y, self.z) {
75            (-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => {
76                Ok(PackedBlockPos::new()
77                    .with_x(self.x)
78                    .with_y(self.y)
79                    .with_z(self.z))
80            }
81            _ => Err(Error(self)),
82        }
83    }
84}
85
86#[bitfield(u64)]
87#[derive(PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
88pub struct PackedBlockPos {
89    #[bits(12)]
90    pub y: i32,
91    #[bits(26)]
92    pub z: i32,
93    #[bits(26)]
94    pub x: i32,
95}
96
97impl Encode for BlockPos {
98    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
99        match self.packed() {
100            Ok(p) => p.encode(w),
101            Err(e) => bail!("{e}: {self}"),
102        }
103    }
104}
105
106impl Decode<'_> for BlockPos {
107    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
108        PackedBlockPos::decode(r).map(Into::into)
109    }
110}
111
112impl From<PackedBlockPos> for BlockPos {
113    fn from(p: PackedBlockPos) -> Self {
114        Self {
115            x: p.x(),
116            y: p.y(),
117            z: p.z(),
118        }
119    }
120}
121
122impl TryFrom<BlockPos> for PackedBlockPos {
123    type Error = Error;
124
125    fn try_from(pos: BlockPos) -> Result<Self, Self::Error> {
126        pos.packed()
127    }
128}
129
130#[derive(Copy, Clone, PartialEq, Eq, Debug, Error, From)]
131#[error("block position of {0} is out of range")]
132pub struct Error(pub BlockPos);
133
134impl From<DVec3> for BlockPos {
135    fn from(pos: DVec3) -> Self {
136        Self {
137            x: pos.x.floor() as i32,
138            y: pos.y.floor() as i32,
139            z: pos.z.floor() as i32,
140        }
141    }
142}
143
144impl From<(i32, i32, i32)> for BlockPos {
145    fn from((x, y, z): (i32, i32, i32)) -> Self {
146        BlockPos::new(x, y, z)
147    }
148}
149
150impl From<BlockPos> for (i32, i32, i32) {
151    fn from(pos: BlockPos) -> Self {
152        (pos.x, pos.y, pos.z)
153    }
154}
155
156impl From<[i32; 3]> for BlockPos {
157    fn from([x, y, z]: [i32; 3]) -> Self {
158        BlockPos::new(x, y, z)
159    }
160}
161
162impl From<BlockPos> for [i32; 3] {
163    fn from(pos: BlockPos) -> Self {
164        [pos.x, pos.y, pos.z]
165    }
166}
167
168impl Add<IVec3> for BlockPos {
169    type Output = Self;
170
171    fn add(self, rhs: IVec3) -> Self::Output {
172        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
173    }
174}
175
176impl Sub<IVec3> for BlockPos {
177    type Output = Self;
178
179    fn sub(self, rhs: IVec3) -> Self::Output {
180        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
181    }
182}
183
184impl Add<BlockPos> for IVec3 {
185    type Output = BlockPos;
186
187    fn add(self, rhs: BlockPos) -> Self::Output {
188        BlockPos::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
189    }
190}
191
192impl Sub<BlockPos> for IVec3 {
193    type Output = BlockPos;
194
195    fn sub(self, rhs: BlockPos) -> Self::Output {
196        BlockPos::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
197    }
198}
199
200impl fmt::Display for BlockPos {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        // Display the block position as a tuple.
203        fmt::Debug::fmt(&(self.x, self.y, self.z), f)
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn block_position() {
213        let xzs = [
214            (-33554432, true),
215            (-33554433, false),
216            (33554431, true),
217            (33554432, false),
218            (0, true),
219            (1, true),
220            (-1, true),
221        ];
222        let ys = [
223            (-2048, true),
224            (-2049, false),
225            (2047, true),
226            (2048, false),
227            (0, true),
228            (1, true),
229            (-1, true),
230        ];
231
232        for (x, x_valid) in xzs {
233            for (y, y_valid) in ys {
234                for (z, z_valid) in xzs {
235                    let pos = BlockPos::new(x, y, z);
236                    if x_valid && y_valid && z_valid {
237                        let c = pos.packed().unwrap();
238                        assert_eq!((c.x(), c.y(), c.z()), (pos.x, pos.y, pos.z));
239                    } else {
240                        assert_eq!(pos.packed(), Err(Error(pos)));
241                    }
242                }
243            }
244        }
245    }
246}