valence_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 derive_more::From;
8use thiserror::Error;
9use valence_math::{DVec3, IVec3};
10
11use crate::direction::Direction;
12use crate::{Decode, Encode};
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 valence_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    pub const fn offset(self, x: i32, y: i32, z: i32) -> Self {
50        Self::new(self.x + x, self.y + y, self.z + z)
51    }
52
53    pub const fn packed(self) -> Result<PackedBlockPos, Error> {
54        match (self.x, self.y, self.z) {
55            (-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => {
56                Ok(PackedBlockPos::new()
57                    .with_x(self.x)
58                    .with_y(self.y)
59                    .with_z(self.z))
60            }
61            _ => Err(Error(self)),
62        }
63    }
64}
65
66#[bitfield(u64)]
67#[derive(PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
68pub struct PackedBlockPos {
69    #[bits(12)]
70    pub y: i32,
71    #[bits(26)]
72    pub z: i32,
73    #[bits(26)]
74    pub x: i32,
75}
76
77impl Encode for BlockPos {
78    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
79        match self.packed() {
80            Ok(p) => p.encode(w),
81            Err(e) => bail!("{e}: {self}"),
82        }
83    }
84}
85
86impl Decode<'_> for BlockPos {
87    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
88        PackedBlockPos::decode(r).map(Into::into)
89    }
90}
91
92impl From<PackedBlockPos> for BlockPos {
93    fn from(p: PackedBlockPos) -> Self {
94        Self {
95            x: p.x(),
96            y: p.y(),
97            z: p.z(),
98        }
99    }
100}
101
102impl TryFrom<BlockPos> for PackedBlockPos {
103    type Error = Error;
104
105    fn try_from(pos: BlockPos) -> Result<Self, Self::Error> {
106        pos.packed()
107    }
108}
109
110#[derive(Copy, Clone, PartialEq, Eq, Debug, Error, From)]
111#[error("block position of {0} is out of range")]
112pub struct Error(pub BlockPos);
113
114impl From<DVec3> for BlockPos {
115    fn from(pos: DVec3) -> Self {
116        Self {
117            x: pos.x.floor() as i32,
118            y: pos.y.floor() as i32,
119            z: pos.z.floor() as i32,
120        }
121    }
122}
123
124impl From<(i32, i32, i32)> for BlockPos {
125    fn from((x, y, z): (i32, i32, i32)) -> Self {
126        BlockPos::new(x, y, z)
127    }
128}
129
130impl From<BlockPos> for (i32, i32, i32) {
131    fn from(pos: BlockPos) -> Self {
132        (pos.x, pos.y, pos.z)
133    }
134}
135
136impl From<[i32; 3]> for BlockPos {
137    fn from([x, y, z]: [i32; 3]) -> Self {
138        BlockPos::new(x, y, z)
139    }
140}
141
142impl From<BlockPos> for [i32; 3] {
143    fn from(pos: BlockPos) -> Self {
144        [pos.x, pos.y, pos.z]
145    }
146}
147
148impl Add<IVec3> for BlockPos {
149    type Output = Self;
150
151    fn add(self, rhs: IVec3) -> Self::Output {
152        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
153    }
154}
155
156impl Sub<IVec3> for BlockPos {
157    type Output = Self;
158
159    fn sub(self, rhs: IVec3) -> Self::Output {
160        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
161    }
162}
163
164impl Add<BlockPos> for IVec3 {
165    type Output = BlockPos;
166
167    fn add(self, rhs: BlockPos) -> Self::Output {
168        BlockPos::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
169    }
170}
171
172impl Sub<BlockPos> for IVec3 {
173    type Output = BlockPos;
174
175    fn sub(self, rhs: BlockPos) -> Self::Output {
176        BlockPos::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
177    }
178}
179
180impl fmt::Display for BlockPos {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        // Display the block position as a tuple.
183        fmt::Debug::fmt(&(self.x, self.y, self.z), f)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn block_position() {
193        let xzs = [
194            (-33554432, true),
195            (-33554433, false),
196            (33554431, true),
197            (33554432, false),
198            (0, true),
199            (1, true),
200            (-1, true),
201        ];
202        let ys = [
203            (-2048, true),
204            (-2049, false),
205            (2047, true),
206            (2048, false),
207            (0, true),
208            (1, true),
209            (-1, true),
210        ];
211
212        for (x, x_valid) in xzs {
213            for (y, y_valid) in ys {
214                for (z, z_valid) in xzs {
215                    let pos = BlockPos::new(x, y, z);
216                    if x_valid && y_valid && z_valid {
217                        let c = pos.packed().unwrap();
218                        assert_eq!((c.x(), c.y(), c.z()), (pos.x, pos.y, pos.z));
219                    } else {
220                        assert_eq!(pos.packed(), Err(Error(pos)));
221                    }
222                }
223            }
224        }
225    }
226}