Pumpkin/pumpkin-world/src/cylindrical_chunk_iterator.rs
2026-08-03 21:16:07 +02:00

134 lines
4.1 KiB
Rust

use std::num::NonZeroU8;
use pumpkin_util::math::vector2::Vector2;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cylindrical {
pub center: Vector2<i32>,
pub view_distance: NonZeroU8,
}
impl Cylindrical {
#[must_use]
pub const fn new(center: Vector2<i32>, view_distance: NonZeroU8) -> Self {
Self {
center,
view_distance,
}
}
pub fn changed_chunks(
old: Self,
new: Self,
) -> (
impl Iterator<Item = Vector2<i32>>,
impl Iterator<Item = Vector2<i32>>,
) {
let loading = new
.all_chunks_within()
.filter(move |c| !old.is_within_distance(c.x, c.y));
let unloading = old
.all_chunks_within()
.filter(move |c| !new.is_within_distance(c.x, c.y));
(loading, unloading)
}
#[allow(dead_code)]
const fn left(&self) -> i32 {
self.center.x - self.view_distance.get() as i32 - 1
}
#[allow(dead_code)]
const fn bottom(&self) -> i32 {
self.center.y - self.view_distance.get() as i32 - 1
}
#[allow(dead_code)]
const fn right(&self) -> i32 {
self.center.x + self.view_distance.get() as i32 + 1
}
#[allow(dead_code)]
const fn top(&self) -> i32 {
self.center.y + self.view_distance.get() as i32 + 1
}
#[must_use]
pub fn is_within_distance(&self, x: i32, z: i32) -> bool {
if self.view_distance.get() == 1 {
return false;
}
let rel_x = ((x - self.center.x).abs() as i64 - 2).max(0);
let rel_z = ((z - self.center.y).abs() as i64 - 2).max(0);
let hyp_sqr = rel_x * rel_x + rel_z * rel_z;
//The view distance should be converted to i64 first because u8 * u8 can overflow
hyp_sqr < (self.view_distance.get() as i64).pow(2)
}
/// Returns a precomputed list of relative chunk offset pairs `(dx, dy)` for a given view distance.
#[must_use]
#[inline]
pub fn get_offsets(view_distance: u8) -> &'static [(i8, i8)] {
let idx =
(view_distance as usize).min(pumpkin_data::chunk_view_lut::MAX_VIEW_DISTANCE as usize);
pumpkin_data::chunk_view_lut::CHUNK_VIEW_LUT[idx]
}
/// Returns an iterator of all chunks within this cylinder using the precomputed LUT
#[must_use]
pub fn all_chunks_within(self) -> impl ExactSizeIterator<Item = Vector2<i32>> {
let offsets = Self::get_offsets(self.view_distance.get());
offsets.iter().map(move |&(dx, dy)| {
Vector2::new(self.center.x + i32::from(dx), self.center.y + i32::from(dy))
})
}
}
#[cfg(test)]
mod test {
use std::num::NonZeroU8;
use super::Cylindrical;
use pumpkin_util::math::vector2::Vector2;
#[test]
fn bounds() {
let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap());
for view_distance in 1..=32 {
cylinder.view_distance = NonZeroU8::new(view_distance).unwrap();
for chunk in cylinder.all_chunks_within() {
assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right());
assert!(chunk.y >= cylinder.bottom() && chunk.y <= cylinder.top());
}
for x in (cylinder.left() - 2)..=(cylinder.right() + 2) {
for z in (cylinder.bottom() - 2)..=(cylinder.top() + 2) {
if cylinder.is_within_distance(x, z) {
assert!(x >= cylinder.left() && x <= cylinder.right());
assert!(z >= cylinder.bottom() && z <= cylinder.top());
}
}
}
}
}
#[test]
fn all_chunks_within_capacity_estimation() {
let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap());
for distance in 1..=64 {
cylinder.view_distance = NonZeroU8::new(distance).unwrap();
let chunks = cylinder.all_chunks_within();
let estimated_capacity = ((distance as usize + 3).pow(2) * 3167) >> 10;
assert!(estimated_capacity >= chunks.len(),);
}
}
}