Skip to main content

steel_utils/types/
packed_position.rs

1use std::{
2    error::Error,
3    fmt::{self, Display, Formatter},
4    io::{self, Cursor, Write},
5};
6
7use glam::{IVec2, IVec3};
8use wincode::{SchemaRead, SchemaWrite};
9
10use crate::serial::{ReadFrom, WriteTo};
11
12use super::position::{BlockPos, ChunkPos, SectionPos};
13
14/// A chunk position in Steel's packed `i64` layout.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
16pub struct PackedChunkPos(i64);
17
18impl PackedChunkPos {
19    /// Creates a packed chunk position from its raw representation.
20    #[must_use]
21    pub const fn from_raw(raw: i64) -> Self {
22        Self(raw)
23    }
24
25    /// Returns the raw packed representation.
26    #[must_use]
27    pub const fn as_raw(self) -> i64 {
28        self.0
29    }
30
31    /// Converts this packed value into a `ChunkPos`.
32    #[must_use]
33    pub const fn to_chunk_pos(self) -> ChunkPos {
34        ChunkPos(IVec2::new(
35            (self.0 & 0xFFFF_FFFF) as i32,
36            (self.0 >> 32) as i32,
37        ))
38    }
39}
40
41impl From<ChunkPos> for PackedChunkPos {
42    fn from(pos: ChunkPos) -> Self {
43        Self((i64::from(pos.0.x) & 0xFFFF_FFFF) | ((i64::from(pos.0.y) & 0xFFFF_FFFF) << 32))
44    }
45}
46
47impl From<PackedChunkPos> for ChunkPos {
48    fn from(pos: PackedChunkPos) -> Self {
49        pos.to_chunk_pos()
50    }
51}
52
53impl ReadFrom for PackedChunkPos {
54    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
55        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
56    }
57}
58
59impl WriteTo for PackedChunkPos {
60    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
61        self.0.write(writer)
62    }
63}
64
65/// A block position in Minecraft's packed protocol `i64` layout.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
67pub struct PackedBlockPos(i64);
68
69impl PackedBlockPos {
70    pub(super) const HORIZONTAL_BITS: u32 = 26;
71    const Y_BITS: u32 = 12;
72    const X_OFFSET: u32 = Self::HORIZONTAL_BITS + Self::Y_BITS;
73    const Z_OFFSET: u32 = Self::Y_BITS;
74    const XZ_MASK: i64 = (1i64 << Self::HORIZONTAL_BITS) - 1;
75    const Y_MASK: i64 = (1i64 << Self::Y_BITS) - 1;
76
77    /// Creates a packed block position from its raw representation.
78    #[must_use]
79    pub const fn from_raw(raw: i64) -> Self {
80        Self(raw)
81    }
82
83    /// Returns the raw packed representation.
84    #[must_use]
85    pub const fn as_raw(self) -> i64 {
86        self.0
87    }
88
89    /// Converts this packed value into a `BlockPos`.
90    #[must_use]
91    pub const fn to_block_pos(self) -> BlockPos {
92        let x = self.0 >> Self::X_OFFSET;
93        let y = self.0 & Self::Y_MASK;
94        let z = (self.0 >> Self::Z_OFFSET) & Self::XZ_MASK;
95
96        let x = (x << (64 - Self::HORIZONTAL_BITS)) >> (64 - Self::HORIZONTAL_BITS);
97        let y = (y << (64 - Self::Y_BITS)) >> (64 - Self::Y_BITS);
98        let z = (z << (64 - Self::HORIZONTAL_BITS)) >> (64 - Self::HORIZONTAL_BITS);
99
100        BlockPos(IVec3::new(x as i32, y as i32, z as i32))
101    }
102}
103
104impl From<BlockPos> for PackedBlockPos {
105    fn from(pos: BlockPos) -> Self {
106        let x = i64::from(pos.0.x);
107        let y = i64::from(pos.0.y);
108        let z = i64::from(pos.0.z);
109        Self(
110            ((x & Self::XZ_MASK) << Self::X_OFFSET)
111                | ((z & Self::XZ_MASK) << Self::Z_OFFSET)
112                | (y & Self::Y_MASK),
113        )
114    }
115}
116
117impl From<PackedBlockPos> for BlockPos {
118    fn from(pos: PackedBlockPos) -> Self {
119        pos.to_block_pos()
120    }
121}
122
123impl ReadFrom for PackedBlockPos {
124    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
125        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
126    }
127}
128
129impl WriteTo for PackedBlockPos {
130    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
131        self.0.write(writer)
132    }
133}
134
135/// A section position in Minecraft's packed `i64` layout.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
137pub struct PackedSectionPos(i64);
138
139impl PackedSectionPos {
140    const XZ_BITS: u32 = 22;
141    const Y_BITS: u32 = 20;
142    const X_OFFSET: u32 = Self::XZ_BITS + Self::Y_BITS;
143    const Z_OFFSET: u32 = Self::Y_BITS;
144    const XZ_MASK: i64 = (1i64 << Self::XZ_BITS) - 1;
145    const Y_MASK: i64 = (1i64 << Self::Y_BITS) - 1;
146
147    /// Creates a packed section position from its raw representation.
148    #[must_use]
149    pub const fn from_raw(raw: i64) -> Self {
150        Self(raw)
151    }
152
153    /// Returns the raw packed representation.
154    #[must_use]
155    pub const fn as_raw(self) -> i64 {
156        self.0
157    }
158
159    /// Converts this packed value into a `SectionPos`.
160    #[must_use]
161    pub const fn to_section_pos(self) -> SectionPos {
162        let x = self.0 >> Self::X_OFFSET;
163        let z = (self.0 >> Self::Z_OFFSET) & Self::XZ_MASK;
164        let y = self.0 & Self::Y_MASK;
165
166        let x = (x << (64 - Self::XZ_BITS)) >> (64 - Self::XZ_BITS);
167        let y = (y << (64 - Self::Y_BITS)) >> (64 - Self::Y_BITS);
168        let z = (z << (64 - Self::XZ_BITS)) >> (64 - Self::XZ_BITS);
169
170        SectionPos(IVec3::new(x as i32, y as i32, z as i32))
171    }
172}
173
174impl From<SectionPos> for PackedSectionPos {
175    fn from(pos: SectionPos) -> Self {
176        let x = i64::from(pos.0.x);
177        let y = i64::from(pos.0.y);
178        let z = i64::from(pos.0.z);
179        Self(
180            ((x & Self::XZ_MASK) << Self::X_OFFSET)
181                | ((z & Self::XZ_MASK) << Self::Z_OFFSET)
182                | (y & Self::Y_MASK),
183        )
184    }
185}
186
187impl From<PackedSectionPos> for SectionPos {
188    fn from(pos: PackedSectionPos) -> Self {
189        pos.to_section_pos()
190    }
191}
192
193impl ReadFrom for PackedSectionPos {
194    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
195        Ok(Self::from_raw(<i64 as ReadFrom>::read(data)?))
196    }
197}
198
199impl WriteTo for PackedSectionPos {
200    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
201        self.0.write(writer)
202    }
203}
204
205/// A block's X/Z position packed relative to its containing chunk.
206///
207/// Layout: `(x << 4) | z`, with each coordinate using 4 bits.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
209pub struct PackedChunkLocalXZ(u8);
210
211impl PackedChunkLocalXZ {
212    const COORD_MASK: u8 = 0x0f;
213
214    /// Packs an absolute block position by masking X and Z to chunk-local range.
215    #[must_use]
216    pub const fn from_block_pos(pos: BlockPos) -> Self {
217        Self::from_local_unchecked(
218            (pos.0.x & SectionPos::SECTION_MASK) as u8,
219            (pos.0.z & SectionPos::SECTION_MASK) as u8,
220        )
221    }
222
223    /// Packs validated chunk-local X/Z coordinates.
224    #[must_use]
225    pub const fn from_local_xz(x: u8, z: u8) -> Option<Self> {
226        if x < 16 && z < 16 {
227            Some(Self::from_local_unchecked(x, z))
228        } else {
229            None
230        }
231    }
232
233    /// Rebuilds a packed chunk-local X/Z position from its raw representation.
234    #[must_use]
235    pub const fn from_raw(raw: u8) -> Self {
236        Self(raw)
237    }
238
239    /// Returns the raw packed representation.
240    #[must_use]
241    pub const fn as_u8(self) -> u8 {
242        self.0
243    }
244
245    /// Returns the chunk-local X coordinate.
246    #[must_use]
247    pub const fn x(self) -> u8 {
248        (self.0 >> 4) & Self::COORD_MASK
249    }
250
251    /// Returns the chunk-local Z coordinate.
252    #[must_use]
253    pub const fn z(self) -> u8 {
254        self.0 & Self::COORD_MASK
255    }
256
257    const fn from_local_unchecked(x: u8, z: u8) -> Self {
258        Self((x << 4) | z)
259    }
260}
261
262impl From<BlockPos> for PackedChunkLocalXZ {
263    fn from(pos: BlockPos) -> Self {
264        Self::from_block_pos(pos)
265    }
266}
267
268impl ReadFrom for PackedChunkLocalXZ {
269    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
270        Ok(Self::from_raw(<u8 as ReadFrom>::read(data)?))
271    }
272}
273
274impl WriteTo for PackedChunkLocalXZ {
275    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
276        self.0.write(writer)
277    }
278}
279
280/// A block position packed relative to its containing 16x16x16 section.
281///
282/// Layout: `(x << 8) | (z << 4) | y`, with each coordinate using 4 bits.
283#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SchemaWrite, SchemaRead)]
284pub struct PackedSectionBlockPos(u16);
285
286impl PackedSectionBlockPos {
287    const COORD_MASK: u16 = 0x0f;
288    const RAW_MASK: u16 = 0x0fff;
289
290    /// Packs an absolute block position by masking each coordinate to section-local range.
291    #[must_use]
292    #[inline]
293    pub const fn from_block_pos(pos: BlockPos) -> Self {
294        Self::from_local_unchecked(
295            (pos.0.x & SectionPos::SECTION_MASK) as u8,
296            (pos.0.y & SectionPos::SECTION_MASK) as u8,
297            (pos.0.z & SectionPos::SECTION_MASK) as u8,
298        )
299    }
300
301    /// Packs validated section-local coordinates.
302    #[must_use]
303    pub const fn from_local_xyz(x: u8, y: u8, z: u8) -> Option<Self> {
304        if x < 16 && y < 16 && z < 16 {
305            Some(Self::from_local_unchecked(x, y, z))
306        } else {
307            None
308        }
309    }
310
311    /// Rebuilds a packed section block position from its raw representation.
312    #[must_use]
313    pub const fn from_raw(raw: u16) -> Option<Self> {
314        if raw & !Self::RAW_MASK == 0 {
315            Some(Self(raw))
316        } else {
317            None
318        }
319    }
320
321    /// Returns the raw packed representation.
322    #[must_use]
323    pub const fn as_u16(self) -> u16 {
324        self.0
325    }
326
327    /// Returns the section-local X coordinate.
328    #[must_use]
329    pub const fn x(self) -> u8 {
330        ((self.0 >> 8) & Self::COORD_MASK) as u8
331    }
332
333    /// Returns the section-local Y coordinate.
334    #[must_use]
335    pub const fn y(self) -> u8 {
336        (self.0 & Self::COORD_MASK) as u8
337    }
338
339    /// Returns the section-local Z coordinate.
340    #[must_use]
341    pub const fn z(self) -> u8 {
342        ((self.0 >> 4) & Self::COORD_MASK) as u8
343    }
344
345    /// Converts this section-relative position to an absolute block position.
346    #[must_use]
347    pub const fn to_block_pos(self, section_pos: SectionPos) -> BlockPos {
348        section_pos.relative_to_block_pos(self)
349    }
350
351    const fn from_local_unchecked(x: u8, y: u8, z: u8) -> Self {
352        Self(((x as u16) << 8) | ((z as u16) << 4) | y as u16)
353    }
354}
355
356impl From<BlockPos> for PackedSectionBlockPos {
357    fn from(pos: BlockPos) -> Self {
358        Self::from_block_pos(pos)
359    }
360}
361
362impl TryFrom<u16> for PackedSectionBlockPos {
363    type Error = InvalidPackedSectionBlockPos;
364
365    fn try_from(raw: u16) -> Result<Self, Self::Error> {
366        Self::from_raw(raw).ok_or(InvalidPackedSectionBlockPos { raw })
367    }
368}
369
370/// Error returned when a raw section-relative block position uses reserved bits.
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub struct InvalidPackedSectionBlockPos {
373    raw: u16,
374}
375
376impl InvalidPackedSectionBlockPos {
377    /// Returns the invalid raw value.
378    #[must_use]
379    pub const fn raw(self) -> u16 {
380        self.raw
381    }
382}
383
384impl Display for InvalidPackedSectionBlockPos {
385    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
386        write!(
387            f,
388            "packed section block position {:#06x} uses reserved bits",
389            self.raw
390        )
391    }
392}
393
394impl Error for InvalidPackedSectionBlockPos {}