use bytes::{BufMut, BytesMut};
use crate::codec::{DecodeResult, EncodeResult, Reader};
use crate::error::EncodeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct BlockPos {
pub x: i32,
pub y: i32,
pub z: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PositionLayout {
Xyz,
Xzy,
}
#[inline]
const fn sign_extend(v: u64, bits: u32) -> i32 {
let shift = 64 - bits;
(((v << shift) as i64) >> shift) as i32
}
#[inline]
const fn fits(v: i32, bits: u32) -> bool {
let min = -(1i64 << (bits - 1));
let max = (1i64 << (bits - 1)) - 1;
(v as i64) >= min && (v as i64) <= max
}
impl BlockPos {
#[inline]
pub const fn new(x: i32, y: i32, z: i32) -> Self {
BlockPos { x, y, z }
}
#[inline]
pub const fn unpack(v: u64, layout: PositionLayout) -> Self {
match layout {
PositionLayout::Xyz => BlockPos {
x: sign_extend(v >> 38, 26),
y: sign_extend((v >> 26) & 0xfff, 12),
z: sign_extend(v & 0x3ff_ffff, 26),
},
PositionLayout::Xzy => BlockPos {
x: sign_extend(v >> 38, 26),
z: sign_extend((v >> 12) & 0x3ff_ffff, 26),
y: sign_extend(v & 0xfff, 12),
},
}
}
#[inline]
pub const fn is_packable(&self) -> bool {
fits(self.x, 26) && fits(self.y, 12) && fits(self.z, 26)
}
#[inline]
pub fn pack(&self, layout: PositionLayout) -> Result<u64, EncodeError> {
if !self.is_packable() {
return Err(EncodeError::OutOfRange("position"));
}
let x = (self.x as u64) & 0x3ff_ffff;
let y = (self.y as u64) & 0xfff;
let z = (self.z as u64) & 0x3ff_ffff;
Ok(match layout {
PositionLayout::Xyz => (x << 38) | (y << 26) | z,
PositionLayout::Xzy => (x << 38) | (z << 12) | y,
})
}
#[inline]
pub fn read(r: &mut Reader<'_>, layout: PositionLayout) -> DecodeResult<Self> {
Ok(Self::unpack(r.read_u64()?, layout))
}
#[inline]
pub fn write(&self, w: &mut BytesMut, layout: PositionLayout) -> EncodeResult {
w.put_u64(self.pack(layout)?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
#[test]
fn known_answers() {
let p = BlockPos::new(18357644, 831, -20882616);
let z = (-20882616i64 as u64) & 0x3ff_ffff;
let xyz = (0x118_1d8cu64 << 38) | (0x33fu64 << 26) | z;
let xzy = (0x118_1d8cu64 << 38) | (z << 12) | 0x33f;
assert_eq!(p.pack(PositionLayout::Xyz), Ok(xyz));
assert_eq!(p.pack(PositionLayout::Xzy), Ok(xzy));
assert_eq!(BlockPos::unpack(xyz, PositionLayout::Xyz), p);
assert_eq!(BlockPos::unpack(xzy, PositionLayout::Xzy), p);
let m = BlockPos::new(-1, -1, -1);
assert_eq!(m.pack(PositionLayout::Xyz), Ok(u64::MAX));
assert_eq!(m.pack(PositionLayout::Xzy), Ok(u64::MAX));
let mut w = BytesMut::new();
BlockPos::new(1, 2, 3)
.write(&mut w, PositionLayout::Xyz)
.unwrap();
assert_eq!(&w[..], &[0x00, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x03]);
let b = Bytes::from(w.to_vec());
assert_eq!(
BlockPos::read(&mut Reader::new(&b), PositionLayout::Xyz),
Ok(BlockPos::new(1, 2, 3))
);
let mut w = BytesMut::new();
BlockPos::new(1, 2, 3)
.write(&mut w, PositionLayout::Xzy)
.unwrap();
assert_eq!(&w[..], &[0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x30, 0x02]);
}
#[test]
fn range_checks() {
assert!(BlockPos::new(33554431, 2047, -33554432).is_packable());
assert_eq!(
BlockPos::new(33554432, 0, 0).pack(PositionLayout::Xyz),
Err(EncodeError::OutOfRange("position"))
);
assert!(BlockPos::new(0, 2048, 0).pack(PositionLayout::Xzy).is_err());
assert!(
BlockPos::new(0, -2049, 0)
.pack(PositionLayout::Xzy)
.is_err()
);
}
}