use roxlap_formats::edit::{set_spans_with_colfunc, SpanOp, Vspan};
use roxlap_formats::vxl::Vxl;
pub const MAXZDIM: i32 = 256;
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn pack_dense_grid_to_vxl(grid: &[u8], color: &[u32], vsid: u32) -> Vxl {
let vsid_u = vsid as usize;
let maxzdim_u = MAXZDIM as usize;
let expected = vsid_u * vsid_u * maxzdim_u;
assert_eq!(grid.len(), expected, "grid size");
assert_eq!(color.len(), expected, "color size");
let n_cols = vsid_u * vsid_u;
let mut data: Vec<u8> = Vec::with_capacity(n_cols * 8);
let mut column_offset: Vec<u32> = Vec::with_capacity(n_cols + 1);
for y in 0..vsid_u {
for x in 0..vsid_u {
column_offset.push(u32::try_from(data.len()).expect("offset fits in u32"));
data.extend_from_slice(&[0, 0, 0, 0]);
let base = (y * vsid_u + x) * maxzdim_u;
let c0 = if grid[base] == 0 { 0 } else { color[base] };
data.extend_from_slice(&c0.to_le_bytes());
}
}
column_offset.push(u32::try_from(data.len()).expect("offset fits in u32"));
let mut vxl = Vxl {
vsid,
ipo: [0.0; 3],
ist: [1.0, 0.0, 0.0],
ihe: [0.0, 0.0, 1.0],
ifo: [0.0, 1.0, 0.0],
data: data.into_boxed_slice(),
column_offset: column_offset.into_boxed_slice(),
mip_base_offsets: Box::new([0, n_cols + 1]),
vbit: Box::new([]),
vbiti: 0,
};
vxl.reserve_edit_capacity(n_cols * 512);
let mut air_spans: Vec<Vspan> = Vec::new();
for y in 0..vsid_u {
for x in 0..vsid_u {
let base = (y * vsid_u + x) * maxzdim_u;
let mut z = 0usize;
while z < maxzdim_u {
if grid[base + z] == 0 {
let zs = z;
while z < maxzdim_u && grid[base + z] == 0 {
z += 1;
}
air_spans.push(Vspan {
x: x as u32,
y: y as u32,
z0: zs as u8,
z1: (z - 1) as u8, });
} else {
z += 1;
}
}
}
}
if !air_spans.is_empty() {
set_spans_with_colfunc(&mut vxl, &air_spans, SpanOp::Carve, |x, y, z| {
let idx = (y as usize * vsid_u + x as usize) * maxzdim_u + z as usize;
#[allow(clippy::cast_possible_wrap)]
let c = color[idx] as i32;
c
});
}
vxl
}
#[cfg(test)]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
mod tests {
use super::*;
use roxlap_formats::edit::expandrle;
fn build_test_grid(
vsid: u32,
solid_predicate: impl Fn(u32, u32, i32) -> bool,
colour_fn: impl Fn(u32, u32, i32) -> u32,
) -> (Vec<u8>, Vec<u32>) {
let vsid_u = vsid as usize;
let maxzdim_u = MAXZDIM as usize;
let n = vsid_u * vsid_u * maxzdim_u;
let mut grid = vec![0u8; n];
let mut color = vec![0u32; n];
for y in 0..vsid {
for x in 0..vsid {
for z in 0..MAXZDIM {
let idx = (y as usize * vsid_u + x as usize) * maxzdim_u + z as usize;
if solid_predicate(x, y, z) {
grid[idx] = 1;
color[idx] = colour_fn(x, y, z);
}
}
}
}
(grid, color)
}
#[test]
fn pack_all_solid_grid_yields_minimal_columns() {
let (grid, color) = build_test_grid(2, |_, _, _| true, |_, _, _| 0x80aa_bbcc);
let vxl = pack_dense_grid_to_vxl(&grid, &color, 2);
assert_eq!(vxl.vsid, 2);
for idx in 0..4 {
let mut b2 = vec![0i32; 256];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 0, "col {idx} run top");
assert_eq!(b2[1], MAXZDIM, "col {idx} run bot");
}
}
#[test]
fn pack_top_carve_yields_z_offset_solid() {
let (grid, color) =
build_test_grid(2, |_, _, z| z >= 50, |_, _, z| 0x8000_0000 | (z as u32));
let vxl = pack_dense_grid_to_vxl(&grid, &color, 2);
for idx in 0..4 {
let mut b2 = vec![0i32; 256];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 50, "col {idx} run top");
assert_eq!(b2[1], MAXZDIM, "col {idx} run bot");
}
}
#[test]
fn pack_cave_in_middle_yields_two_runs() {
let (grid, color) = build_test_grid(
2,
|_, _, z| !(50..100).contains(&z),
|_, _, z| 0x8000_0000 | (z as u32),
);
let vxl = pack_dense_grid_to_vxl(&grid, &color, 2);
for idx in 0..4 {
let mut b2 = vec![0i32; 256];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 0, "col {idx}");
assert_eq!(b2[1], 50, "col {idx}");
assert_eq!(b2[2], 100, "col {idx}");
assert_eq!(b2[3], MAXZDIM, "col {idx}");
}
}
#[test]
fn pack_top_solid_voxel_uses_grid_color() {
let (grid, color) = build_test_grid(
2,
|_, _, z| (64..70).contains(&z),
|_, _, z| 0x8011_2233 | (z as u32),
);
let vxl = pack_dense_grid_to_vxl(&grid, &color, 2);
let column = vxl.column_data(0);
let mut v = 0usize;
let mut top_color = None;
loop {
let nextptr = column[v];
let z1 = column[v + 1];
if z1 == 64 {
let off = v + 4;
top_color = Some(u32::from_le_bytes([
column[off],
column[off + 1],
column[off + 2],
column[off + 3],
]));
break;
}
if nextptr == 0 {
break;
}
v += usize::from(nextptr) * 4;
}
assert_eq!(
top_color,
Some(0x8011_2233 | 64),
"exposed top voxel at z=64 should use grid colour"
);
}
#[test]
fn pack_round_trip_through_worley_grid() {
use crate::{worley_classify_grid, CaveParams};
let params = CaveParams {
seed: 42,
seed_count: 16,
air_ratio: 0.5,
anisotropy: 1.0,
perlin_octaves: 0,
perlin_amplitude: 0.0,
};
let vsid = 8u32;
let grid = worley_classify_grid(¶ms, vsid);
let vsid_u = vsid as usize;
let maxzdim_u = MAXZDIM as usize;
let mut color = vec![0u32; vsid_u * vsid_u * maxzdim_u];
for (i, &g) in grid.iter().enumerate() {
if g != 0 {
let z = (i % maxzdim_u) as u32;
color[i] = 0x8060_4020 | z;
}
}
let vxl = pack_dense_grid_to_vxl(&grid, &color, vsid);
for idx in 0..(vsid_u * vsid_u) {
let mut b2 = vec![0i32; 256];
expandrle(vxl.column_data(idx), &mut b2);
let mut i = 0;
while b2[i + 1] < MAXZDIM {
assert!(
b2[i] < b2[i + 1],
"col {idx} run {i}: top={} bot={}",
b2[i],
b2[i + 1]
);
i += 2;
}
}
}
#[test]
fn pack_grid_size_assert() {
let result = std::panic::catch_unwind(|| {
let grid = vec![0u8; 4];
let color = vec![0u32; 4];
let _ = pack_dense_grid_to_vxl(&grid, &color, 2);
});
assert!(result.is_err(), "wrong grid size should panic");
}
}