#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ChunkCoord {
pub x: i32,
pub z: i32,
}
impl ChunkCoord {
pub const fn new(x: i32, z: i32) -> Self {
Self { x, z }
}
pub fn from_world(wx: f32, wz: f32, chunk_w: f32, chunk_d: f32) -> Self {
Self::new(floor_div(wx, chunk_w), floor_div(wz, chunk_d))
}
pub fn origin_world(self, chunk_w: f32, chunk_d: f32) -> (f32, f32) {
(self.x as f32 * chunk_w, self.z as f32 * chunk_d)
}
pub fn offset(self, dx: i32, dz: i32) -> Self {
Self::new(self.x + dx, self.z + dz)
}
pub fn chebyshev_distance(self, other: ChunkCoord) -> i32 {
let dx = (self.x - other.x).abs();
let dz = (self.z - other.z).abs();
if dx > dz { dx } else { dz }
}
pub fn sq_distance(self, other: ChunkCoord) -> i64 {
let dx = (self.x - other.x) as i64;
let dz = (self.z - other.z) as i64;
dx * dx + dz * dz
}
}
fn floor_div(v: f32, size: f32) -> i32 {
if size <= 0.0 {
return 0;
}
crate::math::floor(v / size) as i32
}
pub fn camera_relative_view(
view: [[f32; 4]; 4],
cam_pos: [f32; 3],
origin: [f32; 3],
) -> [[f32; 4]; 4] {
let d = [
origin[0] - cam_pos[0],
origin[1] - cam_pos[1],
origin[2] - cam_pos[2],
];
let tx = view[0][0] * d[0] + view[1][0] * d[1] + view[2][0] * d[2];
let ty = view[0][1] * d[0] + view[1][1] * d[1] + view[2][1] * d[2];
let tz = view[0][2] * d[0] + view[1][2] * d[1] + view[2][2] * d[2];
[view[0], view[1], view[2], [tx, ty, tz, 1.0]]
}
#[cfg(test)]
mod tests {
use super::*;
fn apply(m: [[f32; 4]; 4], p: [f32; 3]) -> [f32; 3] {
[
m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0],
m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1],
m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2],
]
}
#[test]
fn from_world_maps_positive_positions_to_chunks() {
assert_eq!(
ChunkCoord::from_world(0.0, 0.0, 16.0, 16.0),
ChunkCoord::new(0, 0)
);
assert_eq!(
ChunkCoord::from_world(15.9, 0.0, 16.0, 16.0),
ChunkCoord::new(0, 0)
);
assert_eq!(
ChunkCoord::from_world(16.0, 0.0, 16.0, 16.0),
ChunkCoord::new(1, 0)
);
assert_eq!(
ChunkCoord::from_world(33.0, 48.0, 16.0, 16.0),
ChunkCoord::new(2, 3)
);
}
#[test]
fn from_world_floors_negative_positions() {
assert_eq!(
ChunkCoord::from_world(-0.1, 0.0, 16.0, 16.0),
ChunkCoord::new(-1, 0)
);
assert_eq!(
ChunkCoord::from_world(-16.0, 0.0, 16.0, 16.0),
ChunkCoord::new(-1, 0)
);
assert_eq!(
ChunkCoord::from_world(-16.1, 0.0, 16.0, 16.0),
ChunkCoord::new(-2, 0)
);
assert_eq!(
ChunkCoord::from_world(-1.0, -1.0, 16.0, 16.0),
ChunkCoord::new(-1, -1)
);
}
#[test]
fn from_world_tolerates_a_non_positive_chunk_size() {
assert_eq!(
ChunkCoord::from_world(99.0, 99.0, 0.0, 0.0),
ChunkCoord::new(0, 0)
);
}
#[test]
fn origin_world_round_trips_through_from_world() {
let c = ChunkCoord::new(-3, 5);
let (ox, oz) = c.origin_world(16.0, 16.0);
assert_eq!((ox, oz), (-48.0, 80.0));
assert_eq!(ChunkCoord::from_world(ox, oz, 16.0, 16.0), c);
}
#[test]
fn offset_shifts_the_coordinate() {
assert_eq!(ChunkCoord::new(2, 2).offset(-3, 1), ChunkCoord::new(-1, 3));
}
#[test]
fn chebyshev_distance_is_the_square_ring_metric() {
let c = ChunkCoord::new(0, 0);
assert_eq!(c.chebyshev_distance(ChunkCoord::new(3, 1)), 3);
assert_eq!(c.chebyshev_distance(ChunkCoord::new(-2, 4)), 4);
assert_eq!(c.chebyshev_distance(c), 0);
}
#[test]
fn sq_distance_orders_nearer_chunks_first() {
let cam = ChunkCoord::new(0, 0);
assert!(cam.sq_distance(ChunkCoord::new(1, 0)) < cam.sq_distance(ChunkCoord::new(2, 0)));
assert_eq!(cam.sq_distance(ChunkCoord::new(3, 4)), 25);
}
#[test]
fn camera_relative_view_matches_the_absolute_transform() {
let rot = [
[0.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
let cam = [1000.0, 12.0, -500.0];
let t = apply(rot, [-cam[0], -cam[1], -cam[2]]);
let view = [rot[0], rot[1], rot[2], [t[0], t[1], t[2], 1.0]];
let origin = [992.0, 0.0, -512.0];
let rebased = camera_relative_view(view, cam, origin);
let p = [1005.0, 3.0, -495.0];
let abs = apply(view, p);
let rel = apply(
rebased,
[p[0] - origin[0], p[1] - origin[1], p[2] - origin[2]],
);
for i in 0..3 {
assert!(
(abs[i] - rel[i]).abs() < 1e-3,
"axis {}: absolute {} vs rebased {}",
i,
abs[i],
rel[i]
);
}
}
#[test]
fn camera_relative_view_keeps_the_orientation_columns() {
let view = [
[0.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0, 0.0],
[42.0, 7.0, -9.0, 1.0],
];
let rebased = camera_relative_view(view, [10.0, 0.0, 20.0], [16.0, 0.0, 16.0]);
assert_eq!(rebased[0], view[0]);
assert_eq!(rebased[1], view[1]);
assert_eq!(rebased[2], view[2]);
assert_eq!(rebased[3][3], 1.0);
}
}