use roxlap_formats::vxl::Vxl;
pub const CHUNK_SIZE_Z: u32 = 256;
#[derive(Clone, Copy)]
pub struct GridView<'a> {
pub vsid: u32,
pub chunk_size_xy: u32,
pub chunk_size_z: u32,
pub slab_buf: &'a [u8],
pub column_offsets: &'a [u32],
pub mip_base_offsets: &'a [usize],
pub chunk_grid: Option<&'a ChunkGrid<'a>>,
}
#[derive(Clone, Copy)]
pub struct ChunkGrid<'a> {
pub chunks: &'a [Option<GridView<'a>>],
pub origin_chunk_xy: [i32; 2],
pub origin_chunk_z: i32,
pub chunks_x: u32,
pub chunks_y: u32,
pub chunks_z: u32,
}
impl<'a> GridView<'a> {
#[must_use]
pub fn from_parts(
vsid: u32,
slab_buf: &'a [u8],
column_offsets: &'a [u32],
mip_base_offsets: &'a [usize],
) -> Self {
Self {
vsid,
chunk_size_xy: vsid,
chunk_size_z: CHUNK_SIZE_Z,
slab_buf,
column_offsets,
mip_base_offsets,
chunk_grid: None,
}
}
#[must_use]
pub fn from_single_vxl(vxl: &'a Vxl) -> Self {
Self {
vsid: vxl.vsid,
chunk_size_xy: vxl.vsid,
chunk_size_z: CHUNK_SIZE_Z,
slab_buf: &vxl.data,
column_offsets: &vxl.column_offset,
mip_base_offsets: &vxl.mip_base_offsets,
chunk_grid: None,
}
}
#[must_use]
pub fn from_chunk_grid(chunk_grid: &'a ChunkGrid<'a>, chunk_size_xy: u32) -> Self {
let default_chunk = chunk_grid.chunks.iter().find_map(|c| c.as_ref()).copied();
match default_chunk {
Some(c) => Self {
vsid: c.vsid,
chunk_size_xy,
chunk_size_z: CHUNK_SIZE_Z,
slab_buf: c.slab_buf,
column_offsets: c.column_offsets,
mip_base_offsets: c.mip_base_offsets,
chunk_grid: Some(chunk_grid),
},
None => Self {
vsid: chunk_size_xy,
chunk_size_xy,
chunk_size_z: CHUNK_SIZE_Z,
slab_buf: &[],
column_offsets: &[],
mip_base_offsets: &[],
chunk_grid: Some(chunk_grid),
},
}
}
#[must_use]
pub fn with_chunk_size_xy(mut self, chunk_size_xy: u32) -> Self {
self.chunk_size_xy = chunk_size_xy;
self
}
#[must_use]
pub fn aabb_xy(&self) -> ([i32; 2], [i32; 2]) {
if let Some(cg) = self.chunk_grid {
#[allow(clippy::cast_possible_wrap)]
let cs = self.chunk_size_xy as i32;
#[allow(clippy::cast_possible_wrap)]
let chunks_x = cg.chunks_x as i32;
#[allow(clippy::cast_possible_wrap)]
let chunks_y = cg.chunks_y as i32;
let xmin = cg.origin_chunk_xy[0] * cs;
let ymin = cg.origin_chunk_xy[1] * cs;
let xmax = xmin + chunks_x * cs;
let ymax = ymin + chunks_y * cs;
([xmin, ymin], [xmax, ymax])
} else {
#[allow(clippy::cast_possible_wrap)]
let v = self.vsid as i32;
([0, 0], [v, v])
}
}
#[must_use]
pub fn chunk_at_xy(&self, chunk_idx: [i32; 2]) -> Option<GridView<'a>> {
let z = self.chunk_grid.map_or(0, |cg| cg.origin_chunk_z);
self.chunk_at_xyz([chunk_idx[0], chunk_idx[1], z])
}
#[must_use]
pub fn chunk_at_xyz(&self, chunk_idx: [i32; 3]) -> Option<GridView<'a>> {
if let Some(cg) = self.chunk_grid {
let dx = chunk_idx[0] - cg.origin_chunk_xy[0];
let dy = chunk_idx[1] - cg.origin_chunk_xy[1];
let dz = chunk_idx[2] - cg.origin_chunk_z;
if dx < 0 || dy < 0 || dz < 0 {
return None;
}
#[allow(clippy::cast_sign_loss)]
let (dx, dy, dz) = (dx as u32, dy as u32, dz as u32);
if dx >= cg.chunks_x || dy >= cg.chunks_y || dz >= cg.chunks_z {
return None;
}
let i = (dz as usize * cg.chunks_y as usize + dy as usize) * cg.chunks_x as usize
+ dx as usize;
cg.chunks.get(i).copied().flatten()
} else if chunk_idx[0] == 0 && chunk_idx[1] == 0 {
Some(*self)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_parts_preserves_fields_byte_identically() {
let slab = [0u8, 200, 254, 0];
let cols = [0u32, 4];
let mips = [0usize, 2];
let gv = GridView::from_parts(1, &slab, &cols, &mips);
assert_eq!(gv.vsid, 1);
assert_eq!(gv.slab_buf, &slab[..]);
assert_eq!(gv.column_offsets, &cols[..]);
assert_eq!(gv.mip_base_offsets, &mips[..]);
}
#[test]
fn grid_view_is_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<GridView<'_>>();
}
#[test]
fn from_parts_defaults_chunk_size_xy_to_vsid() {
let mips = [0usize, 2];
let gv = GridView::from_parts(2048, &[], &[], &mips);
assert_eq!(gv.chunk_size_xy, 2048);
}
#[test]
fn chunk_at_xy_returns_self_for_origin_chunk() {
let slab = [0u8, 200, 254, 0];
let cols = [0u32, 4];
let mips = [0usize, 2];
let gv = GridView::from_parts(1, &slab, &cols, &mips);
let inner = gv.chunk_at_xy([0, 0]).expect("origin chunk present");
assert_eq!(inner.vsid, gv.vsid);
assert_eq!(inner.slab_buf, gv.slab_buf);
assert_eq!(inner.column_offsets, gv.column_offsets);
}
#[test]
fn chunk_at_xy_returns_none_for_off_origin_idx() {
let mips = [0usize, 2];
let gv = GridView::from_parts(1, &[], &[], &mips);
assert!(gv.chunk_at_xy([1, 0]).is_none());
assert!(gv.chunk_at_xy([-1, 0]).is_none());
assert!(gv.chunk_at_xy([0, 1]).is_none());
assert!(gv.chunk_at_xy([5, -7]).is_none());
}
#[test]
fn with_chunk_size_xy_overrides_default() {
let mips = [0usize, 2];
let gv = GridView::from_parts(2048, &[], &[], &mips).with_chunk_size_xy(128);
assert_eq!(gv.vsid, 2048);
assert_eq!(gv.chunk_size_xy, 128);
}
fn build_two_chunk_x_stripe() -> ([u8; 4], [u8; 4], [u32; 2], [u32; 2], [usize; 2], [usize; 2])
{
let slab_a = [10u8, 200, 254, 0];
let slab_b = [20u8, 200, 254, 0];
let cols_a = [0u32, 4];
let cols_b = [0u32, 4];
let mips_a = [0usize, 2];
let mips_b = [0usize, 2];
(slab_a, slab_b, cols_a, cols_b, mips_a, mips_b)
}
#[test]
fn chunk_at_xy_via_chunk_grid_returns_in_range_chunks() {
let (slab_a, slab_b, cols_a, cols_b, mips_a, mips_b) = build_two_chunk_x_stripe();
let chunks = [
Some(GridView::from_parts(64, &slab_a, &cols_a, &mips_a)),
Some(GridView::from_parts(64, &slab_b, &cols_b, &mips_b)),
];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
chunks_x: 2,
chunks_y: 1,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 64);
let c0 = gv.chunk_at_xy([0, 0]).expect("chunk [0, 0] present");
assert_eq!(c0.slab_buf, &slab_a[..]);
let c1 = gv.chunk_at_xy([1, 0]).expect("chunk [1, 0] present");
assert_eq!(c1.slab_buf, &slab_b[..]);
assert_ne!(c0.slab_buf, c1.slab_buf);
}
#[test]
fn chunk_at_xy_via_chunk_grid_returns_none_out_of_range() {
let (slab_a, _, cols_a, _, mips_a, _) = build_two_chunk_x_stripe();
let chunks = [
Some(GridView::from_parts(64, &slab_a, &cols_a, &mips_a)),
None,
];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
chunks_x: 2,
chunks_y: 1,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 64);
assert!(gv.chunk_at_xy([-1, 0]).is_none());
assert!(gv.chunk_at_xy([0, -1]).is_none());
assert!(gv.chunk_at_xy([2, 0]).is_none());
assert!(gv.chunk_at_xy([0, 1]).is_none());
assert!(gv.chunk_at_xy([1, 0]).is_none());
}
#[test]
fn chunk_at_xy_handles_negative_origin() {
let (slab_a, slab_b, cols_a, cols_b, mips_a, mips_b) = build_two_chunk_x_stripe();
let chunks = [
Some(GridView::from_parts(64, &slab_a, &cols_a, &mips_a)),
Some(GridView::from_parts(64, &slab_b, &cols_b, &mips_b)),
];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [-1, 0],
chunks_x: 2,
chunks_y: 1,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 64);
let cm1 = gv.chunk_at_xy([-1, 0]).expect("chunk [-1, 0] present");
assert_eq!(cm1.slab_buf, &slab_a[..]);
let c0 = gv.chunk_at_xy([0, 0]).expect("chunk [0, 0] present");
assert_eq!(c0.slab_buf, &slab_b[..]);
assert!(gv.chunk_at_xy([1, 0]).is_none());
assert!(gv.chunk_at_xy([-2, 0]).is_none());
}
#[test]
fn from_chunk_grid_seeds_flat_fields_from_first_populated_chunk() {
let (slab_a, slab_b, cols_a, cols_b, mips_a, mips_b) = build_two_chunk_x_stripe();
let chunks = [
None,
Some(GridView::from_parts(64, &slab_b, &cols_b, &mips_b)),
];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
chunks_x: 2,
chunks_y: 1,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 64);
assert_eq!(gv.slab_buf, &slab_b[..]);
assert_eq!(gv.chunk_size_xy, 64);
let _ = (slab_a, cols_a, mips_a);
}
#[test]
fn aabb_xy_single_chunk_returns_0_to_vsid() {
let mips = [0usize, 2];
let gv = GridView::from_parts(2048, &[], &[], &mips);
let (lo, hi) = gv.aabb_xy();
assert_eq!(lo, [0, 0]);
assert_eq!(hi, [2048, 2048]);
}
#[test]
fn aabb_xy_multi_chunk_covers_full_extent() {
let (slab_a, _, cols_a, _, mips_a, _) = build_two_chunk_x_stripe();
let chunks = [
Some(GridView::from_parts(128, &slab_a, &cols_a, &mips_a)),
None,
None,
None,
None,
None,
];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [-1, 0],
chunks_x: 2,
chunks_y: 3,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 128);
let (lo, hi) = gv.aabb_xy();
assert_eq!(lo, [-128, 0]);
assert_eq!(hi, [128, 384]);
}
#[test]
fn from_chunk_grid_empty_table_falls_back_to_empty_slices() {
let chunks: [Option<GridView<'_>>; 1] = [None];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
chunks_x: 1,
chunks_y: 1,
chunks_z: 1,
origin_chunk_z: 0,
};
let gv = GridView::from_chunk_grid(&cg, 128);
assert!(gv.slab_buf.is_empty());
assert!(gv.column_offsets.is_empty());
assert!(gv.mip_base_offsets.is_empty());
assert_eq!(gv.vsid, 128);
assert_eq!(gv.chunk_size_xy, 128);
assert!(gv.chunk_at_xy([0, 0]).is_none());
}
#[test]
fn chunk_at_xyz_resolves_stacked_chunks() {
let slab0 = [0u8, 100, 100, 0];
let slab1 = [0u8, 200, 200, 0];
let cols = [0u32, 4];
let mips = [0usize, 2];
let c0 = GridView::from_parts(1, &slab0, &cols, &mips);
let c1 = GridView::from_parts(1, &slab1, &cols, &mips);
let chunks = [Some(c0), Some(c1)];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
origin_chunk_z: 0,
chunks_x: 1,
chunks_y: 1,
chunks_z: 2,
};
let gv = GridView::from_chunk_grid(&cg, 1);
let v0 = gv.chunk_at_xyz([0, 0, 0]).expect("chz=0 present");
assert_eq!(v0.slab_buf, &slab0[..]);
let v1 = gv.chunk_at_xyz([0, 0, 1]).expect("chz=1 present");
assert_eq!(v1.slab_buf, &slab1[..]);
assert!(gv.chunk_at_xyz([0, 0, 2]).is_none());
assert!(gv.chunk_at_xyz([0, 0, -1]).is_none());
}
#[test]
fn chunk_at_xy_returns_origin_z_layer() {
let slab_z_neg1 = [0u8, 50, 50, 0];
let slab_z_0 = [0u8, 100, 100, 0];
let cols = [0u32, 4];
let mips = [0usize, 2];
let cn = GridView::from_parts(1, &slab_z_neg1, &cols, &mips);
let c0 = GridView::from_parts(1, &slab_z_0, &cols, &mips);
let chunks = [Some(cn), Some(c0)];
let cg = ChunkGrid {
chunks: &chunks,
origin_chunk_xy: [0, 0],
origin_chunk_z: -1,
chunks_x: 1,
chunks_y: 1,
chunks_z: 2,
};
let gv = GridView::from_chunk_grid(&cg, 1);
let via_xy = gv.chunk_at_xy([0, 0]).expect("origin layer present");
assert_eq!(via_xy.slab_buf, &slab_z_neg1[..]);
let v0 = gv.chunk_at_xyz([0, 0, 0]).expect("chz=0 present");
assert_eq!(v0.slab_buf, &slab_z_0[..]);
}
}