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#[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 pub const fn new(x: i32, y: i32, z: i32) -> Self {
25 Self { x, y, z }
26 }
27
28 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 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}