use crate::{Decode, Encode, EncodedSize, Result};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Position {
pub x: i32,
pub y: i32,
pub z: i32,
}
impl Position {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self { x, y, z }
}
fn pack(&self) -> i64 {
((self.x as i64 & 0x3FFFFFF) << 38)
| ((self.z as i64 & 0x3FFFFFF) << 12)
| (self.y as i64 & 0xFFF)
}
fn unpack(val: i64) -> Self {
let mut x = (val >> 38) as i32;
let mut z = ((val >> 12) & 0x3FFFFFF) as i32;
let mut y = (val & 0xFFF) as i32;
if x >= 1 << 25 {
x -= 1 << 26;
}
if z >= 1 << 25 {
z -= 1 << 26;
}
if y >= 1 << 11 {
y -= 1 << 12;
}
Self { x, y, z }
}
}
impl Encode for Position {
fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
self.pack().encode(buf)
}
}
impl Decode for Position {
fn decode(buf: &mut &[u8]) -> Result<Self> {
let val = i64::decode(buf)?;
Ok(Self::unpack(val))
}
}
impl EncodedSize for Position {
fn encoded_size(&self) -> usize {
8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockPosition {
pub x: i32,
pub y: i32,
pub z: i32,
}
impl BlockPosition {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self { x, y, z }
}
pub fn chunk_pos(&self) -> ChunkPosition {
ChunkPosition {
x: self.x >> 4,
z: self.z >> 4,
}
}
}
impl From<Position> for BlockPosition {
fn from(pos: Position) -> Self {
Self {
x: pos.x,
y: pos.y,
z: pos.z,
}
}
}
impl From<BlockPosition> for Position {
fn from(pos: BlockPosition) -> Self {
Position {
x: pos.x,
y: pos.y,
z: pos.z,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChunkPosition {
pub x: i32,
pub z: i32,
}
impl ChunkPosition {
pub fn new(x: i32, z: i32) -> Self {
Self { x, z }
}
}
impl From<BlockPosition> for ChunkPosition {
fn from(pos: BlockPosition) -> Self {
pos.chunk_pos()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
fn roundtrip(x: i32, y: i32, z: i32) {
let pos = Position::new(x, y, z);
let mut buf = Vec::with_capacity(pos.encoded_size());
pos.encode(&mut buf).unwrap();
assert_eq!(buf.len(), 8);
let mut cursor = buf.as_slice();
let decoded = Position::decode(&mut cursor).unwrap();
assert!(cursor.is_empty());
assert_eq!(decoded, pos);
}
#[test]
fn origin() {
roundtrip(0, 0, 0);
}
#[test]
fn positive_coords() {
roundtrip(100, 64, 200);
}
#[test]
fn negative_coords() {
roundtrip(-100, -32, -200);
}
#[test]
fn max_values() {
roundtrip(33554431, 2047, 33554431);
}
#[test]
fn min_values() {
roundtrip(-33554432, -2048, -33554432);
}
#[test]
fn typical_overworld() {
roundtrip(256, 72, -128);
}
#[test]
fn position_underflow() {
let mut cursor: &[u8] = &[0x01; 7];
assert!(matches!(
Position::decode(&mut cursor),
Err(Error::BufferUnderflow { .. })
));
}
#[test]
fn encoded_size_is_8() {
assert_eq!(Position::new(0, 0, 0).encoded_size(), 8);
assert_eq!(Position::new(100, 200, 300).encoded_size(), 8);
}
#[test]
fn pack_known_value() {
let pos = Position::new(18357644, 831, -20882616);
let packed = pos.pack();
let unpacked = Position::unpack(packed);
assert_eq!(unpacked, pos);
}
#[test]
fn blockpos_to_position() {
let bp = BlockPosition::new(100, 64, -200);
let pos: Position = bp.into();
assert_eq!(pos, Position::new(100, 64, -200));
}
#[test]
fn position_to_blockpos() {
let pos = Position::new(-50, 128, 300);
let bp: BlockPosition = pos.into();
assert_eq!(bp, BlockPosition::new(-50, 128, 300));
}
#[test]
fn blockpos_to_chunkpos() {
let bp = BlockPosition::new(100, 64, -200);
let cp = bp.chunk_pos();
assert_eq!(cp, ChunkPosition::new(6, -13));
}
#[test]
fn blockpos_to_chunkpos_negative() {
let bp = BlockPosition::new(-1, 0, -1);
let cp = bp.chunk_pos();
assert_eq!(cp, ChunkPosition::new(-1, -1));
}
#[test]
fn blockpos_to_chunkpos_origin() {
let bp = BlockPosition::new(0, 0, 0);
let cp = bp.chunk_pos();
assert_eq!(cp, ChunkPosition::new(0, 0));
}
#[test]
fn chunkpos_from_blockpos() {
let bp = BlockPosition::new(32, 0, 48);
let cp: ChunkPosition = bp.into();
assert_eq!(cp, ChunkPosition::new(2, 3));
}
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn position_roundtrip(
x in -33554432i32..=33554431,
y in -2048i32..=2047,
z in -33554432i32..=33554431,
) {
roundtrip(x, y, z);
}
}
}
}