Pumpkin/pumpkin-world/src/cylindrical_chunk_iterator.rs
2024-11-03 22:36:00 +01:00

114 lines
3.6 KiB
Rust

use pumpkin_core::math::vector2::Vector2;
#[derive(Debug, PartialEq)]
pub struct Cylindrical {
pub center: Vector2<i32>,
pub view_distance: u8,
}
impl Cylindrical {
pub fn new(center: Vector2<i32>, view_distance: u8) -> Self {
Self {
center,
view_distance,
}
}
pub fn for_each_changed_chunk(
old_cylindrical: Cylindrical,
new_cylindrical: Cylindrical,
mut newly_included: impl FnMut(Vector2<i32>),
mut just_removed: impl FnMut(Vector2<i32>),
) {
let min_x = old_cylindrical.left().min(new_cylindrical.left());
let max_x = old_cylindrical.right().max(new_cylindrical.right());
let min_z = old_cylindrical.bottom().min(new_cylindrical.bottom());
let max_z = old_cylindrical.top().max(new_cylindrical.top());
//log::debug!("{:?} {:?}", old_cylindrical, new_cylindrical);
for x in min_x..=max_x {
for z in min_z..=max_z {
let old_is_within = old_cylindrical.is_within_distance(x, z);
let new_is_within = new_cylindrical.is_within_distance(x, z);
//log::debug!("{}, {}: {} {}", x, z, old_is_within, new_is_within);
if old_is_within != new_is_within {
if new_is_within {
newly_included(Vector2::new(x, z));
} else {
just_removed(Vector2::new(x, z));
}
}
}
}
}
fn left(&self) -> i32 {
self.center.x - self.view_distance as i32 - 1
}
fn bottom(&self) -> i32 {
self.center.z - self.view_distance as i32 - 1
}
fn right(&self) -> i32 {
self.center.x + self.view_distance as i32 + 1
}
fn top(&self) -> i32 {
self.center.z + self.view_distance as i32 + 1
}
fn is_within_distance(&self, x: i32, z: i32) -> bool {
let rel_x = ((x - self.center.x).abs() - 1).max(0);
let rel_z = ((z - self.center.z).abs() - 1).max(0);
let max_leg = (rel_x.max(rel_z) - 1).max(0) as i64;
let min_leg = rel_x.min(rel_z) as i64;
let hyp_sqr = max_leg * max_leg + min_leg * min_leg;
hyp_sqr < (self.view_distance * self.view_distance) as i64
}
/// Returns an iterator of all chunks within this cylinder
pub fn all_chunks_within(&self) -> Vec<Vector2<i32>> {
// This is a naive implementation: start with square and cut out ones that dont fit
let mut all_chunks = Vec::new();
for x in self.left()..=self.right() {
for z in self.bottom()..=self.top() {
all_chunks.push(Vector2::new(x, z));
}
}
all_chunks
.into_iter()
.filter(|chunk| self.is_within_distance(chunk.x, chunk.z))
.collect()
}
}
#[cfg(test)]
mod test {
use super::Cylindrical;
use pumpkin_core::math::vector2::Vector2;
#[test]
fn test_bounds() {
let cylinder = Cylindrical::new(Vector2::new(0, 0), 10);
for chunk in cylinder.all_chunks_within() {
assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right());
assert!(chunk.z >= cylinder.bottom() && chunk.z <= 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());
}
}
}
}
}