Pumpkin/pumpkin-util/src/math/position.rs
Alexander Medvedev ac4c85192f chore: less ugly code
and less allocations
2026-01-26 22:23:58 +01:00

430 lines
12 KiB
Rust

use super::{
get_section_cord,
vector3::{self, Vector3},
};
use std::fmt;
use std::hash::Hash;
use crate::math::vector2::Vector2;
use num_traits::Euclid;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub struct BlockPosIterator {
start_x: i32,
start_y: i32,
start_z: i32,
end_x: i32,
end_y: i32,
index: usize,
count: usize,
}
impl BlockPosIterator {
pub fn new(
start_x: i32,
start_y: i32,
start_z: i32,
end_x: i32,
end_y: i32,
end_z: i32,
) -> Self {
let count_x = end_x - start_x + 1;
let count_y = end_y - start_y + 1;
let count_z = end_z - start_z + 1;
let count = (count_x * count_y * count_z) as usize;
BlockPosIterator {
start_x,
start_y,
start_z,
end_x,
end_y,
index: 0,
count,
}
}
}
impl Iterator for BlockPosIterator {
type Item = BlockPos;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
return None;
}
let size_x = (self.end_x - self.start_x + 1) as usize;
let size_y = (self.end_y - self.start_y + 1) as usize;
let x_offset = self.index % size_x;
let y_offset = (self.index / size_x) % size_y;
let z_offset = (self.index / size_x) / size_y;
let x = self.start_x + x_offset as i32;
let y = self.start_y + y_offset as i32;
let z = self.start_z + z_offset as i32;
self.index += 1;
Some(BlockPos::new(x, y, z))
}
}
pub struct OutwardIterator {
center_x: i32,
center_y: i32,
center_z: i32,
range_x: i32,
range_y: i32,
range_z: i32,
max_manhattan_distance: i32,
pos: BlockPos,
manhattan_distance: i32,
limit_x: i32,
limit_y: i32,
dx: i32,
dy: i32,
swap_z: bool,
}
impl OutwardIterator {
pub fn new(center: BlockPos, range_x: i32, range_y: i32, range_z: i32) -> Self {
let max_manhattan_distance = range_x + range_y + range_z;
OutwardIterator {
center_x: center.0.x,
center_y: center.0.y,
center_z: center.0.z,
range_x,
range_y,
range_z,
max_manhattan_distance,
pos: BlockPos::ZERO,
manhattan_distance: 0,
limit_x: 0,
limit_y: 0,
dx: 0,
dy: 0,
swap_z: false,
}
}
}
impl Iterator for OutwardIterator {
type Item = BlockPos;
fn next(&mut self) -> Option<Self::Item> {
if self.swap_z {
self.swap_z = false;
self.pos.0.z = self.center_z - (self.pos.0.z - self.center_z);
return Some(self.pos);
}
loop {
if self.dy > self.limit_y {
self.dx += 1;
if self.dx > self.limit_x {
self.manhattan_distance += 1;
if self.manhattan_distance > self.max_manhattan_distance {
return None; // endOfData()
}
self.limit_x = self.range_x.min(self.manhattan_distance);
self.dx = -self.limit_x;
}
self.limit_y = self.range_y.min(self.manhattan_distance - self.dx.abs());
self.dy = -self.limit_y;
}
let i2 = self.dx;
let j2 = self.dy;
let k2 = self.manhattan_distance - i2.abs() - j2.abs();
if k2 <= self.range_z {
self.swap_z = k2 != 0;
self.pos =
BlockPos::new(self.center_x + i2, self.center_y + j2, self.center_z + k2);
self.dy += 1;
return Some(self.pos);
}
self.dy += 1;
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
/// Aka Block Position
pub struct BlockPos(pub Vector3<i32>);
impl BlockPos {
pub const ZERO: Self = Self::new(0, 0, 0);
pub const fn new(x: i32, y: i32, z: i32) -> Self {
Self(Vector3::new(x, y, z))
}
/// Iterates through all `BlockPos` within a cuboid region defined by two corner points,
/// `start` and `end`. The iteration covers all blocks inclusively between the minimum
/// and maximum coordinates of the two provided positions.
///
/// This function ensures that the iteration always proceeds from the smallest to the
/// largest coordinate in each dimension, regardless of whether `start` or `end` has a
/// higher value for a given axis.
///
/// # Arguments
///
/// * `start` - One corner of the cuboid region.
/// * `end` - The opposite corner of the cuboid region.
///
/// # Returns
///
/// A `BlockPosIterator` that yields each `BlockPos` within the defined cuboid.
pub fn iterate(start: BlockPos, end: BlockPos) -> BlockPosIterator {
BlockPosIterator::new(
start.0.x.min(end.0.x),
start.0.y.min(end.0.y),
start.0.z.min(end.0.z),
start.0.x.max(end.0.x),
start.0.y.max(end.0.y),
start.0.z.max(end.0.z),
)
}
// Iterates through `BlockPos` objects outward from a specified `center` point.
/// The iteration order is primarily based on the **Manhattan distance** from the center.
///
/// For positions with the same Manhattan distance, the iteration prioritizes:
/// 1. **Y-offset**: From negative to positive.
/// 2. **X-offset**: From negative to positive (for the same Y-offset).
/// 3. **Z-offset**: Positive Z-offset before negative Z-offset (for the same X and Y offsets).
///
/// # Arguments
///
/// * `center` - The central `BlockPos` from which to start iterating.
/// * `range_x` - The maximum absolute difference allowed in the X-coordinate from the center.
/// * `range_y` - The maximum absolute difference allowed in the Y-coordinate from the center.
/// * `range_z` - The maximum absolute difference allowed in the Z-coordinate from the center.
///
/// # Returns
///
/// An `OutwardIterator` that yields `BlockPos` instances in the described outward order.
pub fn iterate_outwards(
center: BlockPos,
range_x: i32,
range_y: i32,
range_z: i32,
) -> OutwardIterator {
OutwardIterator::new(center, range_x, range_y, range_z)
}
pub fn iterate_block_pos(
start_x: i32,
start_y: i32,
start_z: i32,
end_x: i32,
end_y: i32,
end_z: i32,
) -> BlockPosIterator {
BlockPosIterator::new(start_x, start_y, start_z, end_x, end_y, end_z)
}
pub fn chunk_and_chunk_relative_position(&self) -> (Vector2<i32>, Vector3<i32>) {
(self.chunk_position(), self.chunk_relative_position())
}
pub fn chunk_position(&self) -> Vector2<i32> {
let z_chunk = self.0.z.div_euclid(16);
let x_chunk = self.0.x.div_euclid(16);
Vector2 {
x: x_chunk,
y: z_chunk,
}
}
pub fn chunk_relative_position(&self) -> Vector3<i32> {
let z_chunk = self.0.z.rem_euclid(16);
let x_chunk = self.0.x.rem_euclid(16);
Vector3 {
x: x_chunk,
y: self.0.y,
z: z_chunk,
}
}
pub fn section_relative_position(&self) -> Vector3<i32> {
let (_z_chunk, z_rem) = self.0.z.div_rem_euclid(&16);
let (_x_chunk, x_rem) = self.0.x.div_rem_euclid(&16);
let (_y_chunk, y_rem) = self.0.y.div_rem_euclid(&16);
// Since we divide by 16 remnant can never exceed u8
Vector3 {
x: x_rem,
z: z_rem,
y: y_rem,
}
}
pub fn from_i64(encoded_position: i64) -> Self {
BlockPos(Vector3 {
x: (encoded_position >> 38) as i32,
y: (encoded_position << 52 >> 52) as i32,
z: (encoded_position << 26 >> 38) as i32,
})
}
pub fn floored(x: f64, y: f64, z: f64) -> Self {
Self(Vector3::new(
x.floor() as i32,
y.floor() as i32,
z.floor() as i32,
))
}
pub fn floored_v(pos: Vector3<f64>) -> Self {
Self(Vector3::new(
pos.x.floor() as i32,
pos.y.floor() as i32,
pos.z.floor() as i32,
))
}
pub fn ceiled_v(pos: Vector3<f64>) -> Self {
Self(Vector3::new(
pos.x.ceil() as i32,
pos.y.ceil() as i32,
pos.z.ceil() as i32,
))
}
pub fn to_f64(&self) -> Vector3<f64> {
Vector3::new(
self.0.x as f64 + 0.5,
self.0.y as f64,
self.0.z as f64 + 0.5,
)
}
pub fn to_centered_f64(&self) -> Vector3<f64> {
Vector3::new(
self.0.x as f64 + 0.5,
self.0.y as f64 + 0.5,
self.0.z as f64 + 0.5,
)
}
pub fn offset(&self, offset: Vector3<i32>) -> Self {
BlockPos(self.0 + offset)
}
pub fn add(&self, x: i32, y: i32, z: i32) -> Self {
BlockPos::new(self.0.x + x, self.0.y + y, self.0.z + z)
}
pub fn offset_dir(&self, offset: Vector3<i32>, direction: i32) -> Self {
BlockPos(Vector3::new(
self.0.x + offset.x * direction,
self.0.y + offset.y * direction,
self.0.z + offset.z * direction,
))
}
pub fn up(&self) -> Self {
self.offset(Vector3::new(0, 1, 0))
}
pub fn up_height(&self, height: i32) -> Self {
self.offset(Vector3::new(0, height, 0))
}
pub fn down(&self) -> Self {
self.offset(Vector3::new(0, -1, 0))
}
pub fn down_height(&self, height: i32) -> Self {
self.offset(Vector3::new(0, -height, 0))
}
pub fn west(&self) -> Self {
self.offset(Vector3::new(-1, 0, 0))
}
pub fn north(&self) -> Self {
self.offset(Vector3::new(0, 0, -1))
}
pub fn east(&self) -> Self {
self.offset(Vector3::new(1, 0, 0))
}
pub fn south(&self) -> Self {
self.offset(Vector3::new(0, 0, 1))
}
pub fn manhattan_distance(&self, other: Self) -> i32 {
let x = (other.0.x - self.0.x).abs();
let y = (other.0.y - self.0.y).abs();
let z = (other.0.z - self.0.z).abs();
x + y + z
}
pub fn squared_distance(&self, other: &Self) -> i32 {
self.0.squared_distance_to_vec(&other.0)
}
pub fn as_long(&self) -> i64 {
((self.0.x as i64 & 0x3FFFFFF) << 38)
| ((self.0.z as i64 & 0x3FFFFFF) << 12)
| (self.0.y as i64 & 0xFFF)
}
}
impl Serialize for BlockPos {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_i64(self.as_long())
}
}
impl<'de> Deserialize<'de> for BlockPos {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = BlockPos;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("An i64 int")
}
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
Ok(BlockPos(Vector3 {
x: (v >> 38) as i32,
y: (v << 52 >> 52) as i32,
z: (v << 26 >> 38) as i32,
}))
}
}
deserializer.deserialize_i64(Visitor)
}
}
impl fmt::Display for BlockPos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}, {}", self.0.x, self.0.y, self.0.z)
}
}
#[must_use]
pub const fn chunk_section_from_pos(block_pos: &BlockPos) -> Vector3<i32> {
let block_pos = block_pos.0;
Vector3::new(
get_section_cord(block_pos.x),
get_section_cord(block_pos.y),
get_section_cord(block_pos.z),
)
}
pub const fn get_local_cord(cord: i32) -> i32 {
cord & 15
}
#[must_use]
pub fn pack_local_chunk_section(block_pos: &BlockPos) -> i16 {
let x = get_local_cord(block_pos.0.x);
let z = get_local_cord(block_pos.0.z);
let y = get_local_cord(block_pos.0.y);
vector3::packed_local(&Vector3::new(x, y, z))
}