#![allow(dead_code)]
pub const MAXZDIM: i32 = 256;
pub fn delslab(b2: &mut [i32], y0: i32, mut y1: i32) {
if y1 >= MAXZDIM {
y1 = MAXZDIM - 1;
}
if y0 >= y1 || b2.is_empty() {
return;
}
let mut z = 0usize;
while y0 >= b2[z + 1] {
z += 2;
}
if y0 > b2[z] {
if y1 < b2[z + 1] {
let mut i = z;
while b2[i + 1] < MAXZDIM {
i += 2;
}
while i > z {
b2[i + 3] = b2[i + 1];
b2[i + 2] = b2[i];
i -= 2;
}
b2[z + 3] = b2[z + 1];
b2[z + 1] = y0;
b2[z + 2] = y1;
return;
}
b2[z + 1] = y0;
z += 2;
}
if y1 >= b2[z + 1] {
let mut i = z + 2;
while y1 >= b2[i + 1] {
i += 2;
}
let delta = i - z;
b2[z] = b2[i];
b2[z + 1] = b2[i + 1];
while b2[i + 1] < MAXZDIM {
i += 2;
b2[i - delta] = b2[i];
b2[i - delta + 1] = b2[i + 1];
}
}
if y1 > b2[z] {
b2[z] = y1;
}
}
pub fn insslab(b2: &mut [i32], y0: i32, y1: i32) {
if y0 >= y1 || b2.is_empty() {
return;
}
let mut z = 0usize;
while y0 > b2[z + 1] {
z += 2;
}
if y1 < b2[z] {
let mut i = z;
while b2[i + 1] < MAXZDIM {
i += 2;
}
loop {
b2[i + 3] = b2[i + 1];
b2[i + 2] = b2[i];
if i == z {
break;
}
i -= 2;
}
b2[z + 1] = y1;
b2[z] = y0;
return;
}
if y0 < b2[z] {
b2[z] = y0;
}
if y1 >= b2[z + 2] && b2[z + 1] < MAXZDIM {
let mut i = z + 2;
while y1 >= b2[i + 2] && b2[i + 1] < MAXZDIM {
i += 2;
}
let delta = i - z;
b2[z + 1] = b2[i + 1];
while b2[i + 1] < MAXZDIM {
i += 2;
b2[i - delta] = b2[i];
b2[i - delta + 1] = b2[i + 1];
}
}
if y1 > b2[z + 1] {
b2[z + 1] = y1;
}
}
pub fn expandrle(slab: &[u8], uind: &mut [i32]) {
uind[0] = i32::from(slab[1]);
let mut i = 2usize;
let mut v = 0usize;
while slab[v] != 0 {
v += usize::from(slab[v]) * 4;
if slab[v + 3] >= slab[v + 1] {
continue;
}
uind[i - 1] = i32::from(slab[v + 3]);
uind[i] = i32::from(slab[v + 1]);
i += 2;
}
uind[i - 1] = MAXZDIM;
}
#[derive(Debug)]
struct ColorRange<'s> {
z_start: i32,
z_end: i32,
colors: &'s [u8],
}
fn build_color_table(slab: &[u8]) -> Vec<ColorRange<'_>> {
let mut ranges = Vec::new();
let mut v = 0usize;
loop {
let z_start = i32::from(slab[v + 1]);
let z1c = i32::from(slab[v + 2]);
let z_end = z1c + 1;
let n_voxels = usize::try_from((z_end - z_start).max(0)).expect("voxel count >= 0");
let off = v + 4;
ranges.push(ColorRange {
z_start,
z_end,
colors: &slab[off..off + n_voxels * 4],
});
let nextptr = slab[v];
if nextptr == 0 {
break;
}
let prev_v = v;
v += usize::from(nextptr) * 4;
let ze = i32::from(slab[v + 3]);
let prev_z1 = i32::from(slab[prev_v + 1]);
let prev_z1c = i32::from(slab[prev_v + 2]);
let prev_nextptr = i32::from(slab[prev_v]);
let ceil_z_start = ze + prev_z1c - prev_z1 - prev_nextptr + 2;
let ceil_z_end = ze;
let ceil_n =
usize::try_from((ceil_z_end - ceil_z_start).max(0)).expect("ceiling voxel count >= 0");
let ceil_start = v - ceil_n * 4;
ranges.push(ColorRange {
z_start: ceil_z_start,
z_end: ceil_z_end,
colors: &slab[ceil_start..v],
});
}
ranges.push(ColorRange {
z_start: MAXZDIM,
z_end: MAXZDIM,
colors: &[],
});
ranges
}
#[allow(
clippy::too_many_arguments,
clippy::too_many_lines,
clippy::missing_panics_doc
)]
pub(crate) fn compilerle(
n0: &[i32],
n1: &[i32],
n2: &[i32],
n3: &[i32],
n4: &[i32],
cbuf: &mut [u8],
original_column: &[u8],
px: i32,
py: i32,
colfunc: &mut dyn FnMut(i32, i32, i32) -> i32,
) -> usize {
let tbuf2 = build_color_table(original_column);
let mut p_z: i32 = n0[0];
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let to_u8 = |v: i32| (v & 0xff) as u8;
cbuf[1] = to_u8(p_z);
let mut ze: i32 = n0[1];
cbuf[2] = to_u8(ze - 1);
cbuf[3] = 0;
let mut i = 0usize;
let mut onext = 0usize;
let mut ic = 0usize;
let mut ia: i32 = 15;
let mut n = 4usize;
let mut zend = if ze == MAXZDIM { -1 } else { ze - 1 };
let mut n1_idx = 0usize;
let mut n2_idx = 0usize;
let mut n3_idx = 0usize;
let mut n4_idx = 0usize;
'outer: loop {
let mut dacnt = 0;
'middle: loop {
let exit_to_rlendit2 = loop {
while p_z >= tbuf2[ic].z_end {
ic += 1;
}
let color: i32 = if p_z >= tbuf2[ic].z_start {
let off =
usize::try_from((p_z - tbuf2[ic].z_start) * 4).expect("color offset >= 0");
let bytes = &tbuf2[ic].colors[off..off + 4];
i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
} else {
colfunc(px, py, p_z)
};
cbuf[n..n + 4].copy_from_slice(&color.to_le_bytes());
n += 4;
p_z += 1;
if p_z >= ze {
break true; }
while p_z >= n1[n1_idx] {
n1_idx += 1;
ia ^= 1;
}
while p_z >= n2[n2_idx] {
n2_idx += 1;
ia ^= 2;
}
while p_z >= n3[n3_idx] {
n3_idx += 1;
ia ^= 4;
}
while p_z >= n4[n4_idx] {
n4_idx += 1;
ia ^= 8;
}
if !(ia != 0 || p_z == zend) {
break false; }
};
if exit_to_rlendit2 {
if ze >= MAXZDIM {
break 'outer;
}
i += 2;
cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
onext = n;
p_z = n0[i];
cbuf[n + 1] = to_u8(p_z);
cbuf[n + 3] = to_u8(ze);
ze = n0[i + 1];
cbuf[n + 2] = to_u8(ze - 1);
n += 4;
zend = if ze == MAXZDIM { -1 } else { ze - 1 };
break 'middle; }
if dacnt == 0 {
cbuf[onext + 2] = to_u8(p_z - 1);
dacnt = 1;
} else {
cbuf[onext] = u8::try_from((n - onext) >> 2).expect("slab dword count fits in u8");
onext = n;
cbuf[n + 1] = to_u8(p_z);
cbuf[n + 2] = to_u8(p_z - 1);
cbuf[n + 3] = to_u8(p_z);
n += 4;
}
let n1_v = n1[n1_idx];
let n2_v = n2[n2_idx];
let n3_v = n3[n3_idx];
let n4_v = n4[n4_idx];
if n1_v < n2_v && n1_v < n3_v && n1_v < n4_v {
if n1_v >= ze {
p_z = ze - 1;
} else {
p_z = n1_v;
n1_idx += 1;
ia ^= 1;
}
} else if n2_v < n3_v && n2_v < n4_v {
if n2_v >= ze {
p_z = ze - 1;
} else {
p_z = n2_v;
n2_idx += 1;
ia ^= 2;
}
} else if n3_v < n4_v {
if n3_v >= ze {
p_z = ze - 1;
} else {
p_z = n3_v;
n3_idx += 1;
ia ^= 4;
}
} else if n4_v >= ze {
p_z = ze - 1;
} else {
p_z = n4_v;
n4_idx += 1;
ia ^= 8;
}
if p_z == MAXZDIM - 1 {
break 'outer;
}
}
}
cbuf[onext] = 0;
n
}
use crate::vxl::Vxl;
pub(crate) const SCPITCH: usize = 256;
pub(crate) const MAXCSIZ: usize = 1028;
const SCOY_NONE: i32 = i32::MIN;
const SCOYM3_INITIAL: usize = SCPITCH * 6;
const SCOYM3_WRAP: usize = SCPITCH * 9;
pub struct ScumCtx<'v> {
vxl: &'v mut Vxl,
radar: Vec<i32>,
cbuf: Vec<u8>,
colfunc: Box<dyn FnMut(i32, i32, i32) -> i32 + 'v>,
scoy: i32,
scoym3: usize,
scx0: i32,
scx1: i32,
scox0: i32,
scox1: i32,
scoox0: i32,
scoox1: i32,
scex0: i32,
scex1: i32,
sceox0: i32,
sceox1: i32,
last_scum2: Option<(i32, i32)>,
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::if_not_else,
clippy::similar_names
)]
impl<'v> ScumCtx<'v> {
pub fn new(vxl: &'v mut Vxl) -> Self {
assert!(
!vxl.vbit.is_empty(),
"ScumCtx::new requires Vxl::reserve_edit_capacity to be called first"
);
let radar_size = (vxl.vsid as usize + 4) * 3 * SCPITCH;
Self {
vxl,
radar: vec![0i32; radar_size],
cbuf: vec![0u8; MAXCSIZ],
colfunc: Box::new(|_, _, _| 0),
scoy: SCOY_NONE,
scoym3: SCOYM3_INITIAL,
scx0: 0,
scx1: 0,
scox0: 0,
scox1: 0,
scoox0: 0,
scoox1: 0,
scex0: 0,
scex1: 0,
sceox0: 0,
sceox1: 0,
last_scum2: None,
}
}
pub fn set_colfunc<F>(&mut self, f: F)
where
F: FnMut(i32, i32, i32) -> i32 + 'v,
{
self.colfunc = Box::new(f);
}
pub fn scum2(&mut self, x: i32, y: i32) -> Option<&mut [i32]> {
let vsid = self.vxl.vsid as i32;
if x < 0 || x >= vsid || y < 0 || y >= vsid {
return None;
}
if y != self.scoy {
if self.scoy != SCOY_NONE {
self.scum2_line();
while self.scoy < y - 1 {
self.scx0 = i32::MAX;
self.scx1 = i32::MIN;
self.advance_row();
self.scum2_line();
}
self.advance_row();
} else {
self.scoox0 = i32::MAX;
self.scox0 = i32::MAX;
self.sceox0 = x + 1;
self.scex0 = x + 1;
self.sceox1 = x;
self.scex1 = x;
self.scoy = y;
self.scoym3 = SCOYM3_INITIAL;
}
self.scx0 = x;
} else {
while self.scx1 < x - 1 {
self.scx1 += 1;
let scx1 = self.scx1;
self.expand_column_into_row(scx1, y, self.scoym3);
}
}
let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
self.scx1 = x;
self.expand_column_into_row(x, y, self.scoym3);
self.last_scum2 = Some((x, y));
Some(&mut self.radar[radar_idx..radar_idx + SCPITCH])
}
pub fn with_column<F>(&mut self, x: i32, y: i32, f: F) -> bool
where
F: FnOnce(&mut [i32]),
{
if self.last_scum2 != Some((x, y)) && self.scum2(x, y).is_none() {
return false;
}
let radar_idx = self.scoym3 + (x as usize) * SCPITCH * 3;
let b2 = &mut self.radar[radar_idx..radar_idx + SCPITCH];
f(b2);
true
}
pub fn finish(mut self) {
if self.scoy == SCOY_NONE {
return;
}
for _ in 0..2 {
self.scum2_line();
self.scx0 = i32::MAX;
self.scx1 = i32::MIN;
self.advance_row();
}
self.scum2_line();
self.scoy = SCOY_NONE;
}
fn advance_row(&mut self) {
self.scoy += 1;
self.scoym3 += SCPITCH;
if self.scoym3 == SCOYM3_WRAP {
self.scoym3 = SCOYM3_INITIAL;
}
self.last_scum2 = None;
}
fn expand_column_into_row(&mut self, x: i32, y: i32, row_base: usize) {
let vsid = self.vxl.vsid as i32;
let radar_idx_signed = (row_base as isize) + (x as isize) * (SCPITCH as isize) * 3;
if radar_idx_signed < 0 {
return;
}
#[allow(clippy::cast_sign_loss)]
let radar_idx = radar_idx_signed as usize;
if radar_idx + SCPITCH > self.radar.len() {
return;
}
if x < 0 || x >= vsid || y < 0 || y >= vsid {
self.radar[radar_idx] = 0;
self.radar[radar_idx + 1] = MAXZDIM;
return;
}
let idx = (y as usize) * (vsid as usize) + (x as usize);
let slab = self.vxl.column_data(idx);
expandrle(slab, &mut self.radar[radar_idx..radar_idx + SCPITCH]);
}
#[allow(clippy::too_many_lines)]
fn scum2_line(&mut self) {
let vsid = self.vxl.vsid as i32;
let x0 = (self.scox0 - 1).min(self.scx0).min(self.scoox0);
self.scoox0 = self.scox0;
self.scox0 = self.scx0;
let x1 = (self.scox1 + 1).max(self.scx1).max(self.scoox1);
self.scoox1 = self.scox1;
self.scox1 = self.scx1;
let uptr = wrap_radar(self.scoym3 + SCPITCH);
let mptr = wrap_radar(uptr + SCPITCH);
let scoy_2 = self.scoy - 2;
if x1 < self.sceox0 || x0 > self.sceox1 {
for x in x0..=x1 {
self.expand_column_into_row(x, scoy_2, uptr);
}
} else {
for x in x0..self.sceox0 {
self.expand_column_into_row(x, scoy_2, uptr);
}
let mut x = x1;
while x > self.sceox1 {
self.expand_column_into_row(x, scoy_2, uptr);
x -= 1;
}
}
let scoy_1 = self.scoy - 1;
if (self.scex1 | x1) >= 0 {
for x in (x1 + 2)..self.scex0 {
self.expand_column_into_row(x, scoy_1, mptr);
}
let mut x = x0 - 2;
while x > self.scex1 {
self.expand_column_into_row(x, scoy_1, mptr);
x -= 1;
}
}
if x1 + 1 < self.scex0 || x0 - 1 > self.scex1 {
for x in (x0 - 1)..=(x1 + 1) {
self.expand_column_into_row(x, scoy_1, mptr);
}
} else {
for x in (x0 - 1)..self.scex0 {
self.expand_column_into_row(x, scoy_1, mptr);
}
let mut x = x1 + 1;
while x > self.scex1 {
self.expand_column_into_row(x, scoy_1, mptr);
x -= 1;
}
}
self.sceox0 = (x0 - 1).min(self.scex0);
self.sceox1 = (x1 + 1).max(self.scex1);
let scoy_0 = self.scoy;
let scoym3 = self.scoym3;
if x1 < self.scx0 || x0 > self.scx1 {
for x in x0..=x1 {
self.expand_column_into_row(x, scoy_0, scoym3);
}
} else {
for x in x0..self.scx0 {
self.expand_column_into_row(x, scoy_0, scoym3);
}
let mut x = x1;
while x > self.scx1 {
self.expand_column_into_row(x, scoy_0, scoym3);
x -= 1;
}
}
self.scex0 = x0;
self.scex1 = x1;
let y = self.scoy - 1;
if !(0..vsid).contains(&y) {
return;
}
let x0_clamped = x0.max(0);
let x1_clamped = x1.min(vsid - 1);
for x in x0_clamped..=x1_clamped {
self.flush_column(x, y, mptr, uptr, scoym3);
}
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn flush_column(&mut self, x: i32, y: i32, mptr: usize, uptr: usize, scoym3: usize) {
let vsid = self.vxl.vsid as usize;
let k = (x as usize) * SCPITCH * 3;
let n0_pos = mptr + k;
let n1_pos_signed = (mptr as isize) + (k as isize) - (SCPITCH as isize) * 3;
let n2_pos = mptr + k + SCPITCH * 3;
let n3_pos = uptr + k;
let n4_pos = scoym3 + k;
if n1_pos_signed < 0 {
return;
}
let n1_pos = n1_pos_signed as usize;
let idx = (y as usize) * vsid + (x as usize);
let original_bytes: Vec<u8> = self.vxl.column_data(idx).to_vec();
let written = {
let radar = &self.radar;
let n0 = &radar[n0_pos..n0_pos + SCPITCH];
let n1 = &radar[n1_pos..n1_pos + SCPITCH];
let n2 = &radar[n2_pos..n2_pos + SCPITCH];
let n3 = &radar[n3_pos..n3_pos + SCPITCH];
let n4 = &radar[n4_pos..n4_pos + SCPITCH];
compilerle(
n0,
n1,
n2,
n3,
n4,
&mut self.cbuf,
&original_bytes,
x,
y,
&mut *self.colfunc,
)
};
let old_offset = self.vxl.column_offset[idx];
self.vxl.voxdealloc(old_offset);
let new_offset = self.vxl.voxalloc(written as u32);
self.vxl.data[new_offset as usize..new_offset as usize + written]
.copy_from_slice(&self.cbuf[..written]);
self.vxl.column_offset[idx] = new_offset;
}
}
fn wrap_radar(off: usize) -> usize {
if off == SCOYM3_WRAP {
SCOYM3_INITIAL
} else {
off
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Vspan {
pub x: u32,
pub y: u32,
pub z0: u8,
pub z1: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpanOp {
Carve,
Insert,
}
#[allow(clippy::cast_possible_wrap)]
pub fn set_spans_with_colfunc<F>(world: &mut Vxl, spans: &[Vspan], op: SpanOp, colfunc: F)
where
F: FnMut(i32, i32, i32) -> i32,
{
if spans.is_empty() {
return;
}
let inserting = op == SpanOp::Insert;
let mut ctx = ScumCtx::new(world);
ctx.set_colfunc(colfunc);
for span in spans {
let x = span.x as i32;
let y = span.y as i32;
let z0 = i32::from(span.z0);
let z1 = i32::from(span.z1) + 1; ctx.with_column(x, y, |b2| {
if inserting {
insslab(b2, z0, z1);
} else {
delslab(b2, z0, z1);
}
});
}
ctx.finish();
}
pub fn set_spans(world: &mut Vxl, spans: &[Vspan], color: Option<u32>) {
let op = if color.is_some() {
SpanOp::Insert
} else {
SpanOp::Carve
};
#[allow(clippy::cast_possible_wrap)]
let c_i32 = color.unwrap_or(0) as i32;
set_spans_with_colfunc(world, spans, op, move |_, _, _| c_i32);
}
pub fn set_cube(world: &mut Vxl, x: i32, y: i32, z: i32, color: Option<u32>) {
let op = if color.is_some() {
SpanOp::Insert
} else {
SpanOp::Carve
};
#[allow(clippy::cast_possible_wrap)]
let c_i32 = color.unwrap_or(0) as i32;
set_cube_with_colfunc(world, x, y, z, op, move |_, _, _| c_i32);
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
pub fn set_cube_with_colfunc<F>(world: &mut Vxl, x: i32, y: i32, z: i32, op: SpanOp, colfunc: F)
where
F: FnMut(i32, i32, i32) -> i32,
{
let vsid = world.vsid as i32;
if x < 0 || x >= vsid || y < 0 || y >= vsid || !(0..MAXZDIM).contains(&z) {
return;
}
let span = Vspan {
x: x as u32,
y: y as u32,
z0: z as u8,
z1: z as u8,
};
set_spans_with_colfunc(world, &[span], op, colfunc);
}
pub fn set_rect(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], color: Option<u32>) {
let op = if color.is_some() {
SpanOp::Insert
} else {
SpanOp::Carve
};
#[allow(clippy::cast_possible_wrap)]
let c_i32 = color.unwrap_or(0) as i32;
set_rect_with_colfunc(world, lo, hi, op, move |_, _, _| c_i32);
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
pub fn set_rect_with_colfunc<F>(world: &mut Vxl, lo: [i32; 3], hi: [i32; 3], op: SpanOp, colfunc: F)
where
F: FnMut(i32, i32, i32) -> i32,
{
let vsid = world.vsid as i32;
let xs = lo[0].min(hi[0]).max(0);
let xe = lo[0].max(hi[0]).min(vsid - 1);
let ys = lo[1].min(hi[1]).max(0);
let ye = lo[1].max(hi[1]).min(vsid - 1);
let zs = lo[2].min(hi[2]).max(0);
let ze = lo[2].max(hi[2]).min(MAXZDIM - 1);
if xs > xe || ys > ye || zs > ze {
return;
}
let inserting = op == SpanOp::Insert;
let mut ctx = ScumCtx::new(world);
ctx.set_colfunc(colfunc);
for y in ys..=ye {
for x in xs..=xe {
ctx.with_column(x, y, |b2| {
if inserting {
insslab(b2, zs, ze + 1);
} else {
delslab(b2, zs, ze + 1);
}
});
}
}
ctx.finish();
}
pub fn set_sphere(world: &mut Vxl, center: [i32; 3], radius: u32, color: Option<u32>) {
let op = if color.is_some() {
SpanOp::Insert
} else {
SpanOp::Carve
};
#[allow(clippy::cast_possible_wrap)]
let c_i32 = color.unwrap_or(0) as i32;
set_sphere_with_colfunc(world, center, radius, op, move |_, _, _| c_i32);
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::similar_names
)]
pub fn set_sphere_with_colfunc<F>(
world: &mut Vxl,
center: [i32; 3],
radius: u32,
op: SpanOp,
colfunc: F,
) where
F: FnMut(i32, i32, i32) -> i32,
{
let vsid = world.vsid as i32;
let cx = center[0];
let cy = center[1];
let cz = center[2];
let r = radius as i32;
let xs = (cx - r).max(0);
let xe = (cx + r).min(vsid - 1);
let ys = (cy - r).max(0);
let ye = (cy + r).min(vsid - 1);
let zs = (cz - r).max(0);
let ze = (cz + r).min(MAXZDIM - 1);
if xs > xe || ys > ye || zs > ze {
return;
}
let r_sq = r * r;
let inserting = op == SpanOp::Insert;
let mut ctx = ScumCtx::new(world);
ctx.set_colfunc(colfunc);
for y in ys..=ye {
let dy = y - cy;
let dy_sq = dy * dy;
if dy_sq > r_sq {
continue;
}
for x in xs..=xe {
let dx = x - cx;
let dx_sq = dx * dx;
let xy_sq = dx_sq + dy_sq;
if xy_sq > r_sq {
continue;
}
let dz_max_sq = r_sq - xy_sq;
let dz_max = (dz_max_sq as f32).sqrt() as i32;
let z_lo = (cz - dz_max).max(zs);
let z_hi = (cz + dz_max).min(ze);
if z_lo > z_hi {
continue;
}
ctx.with_column(x, y, |b2| {
if inserting {
insslab(b2, z_lo, z_hi + 1);
} else {
delslab(b2, z_lo, z_hi + 1);
}
});
}
}
ctx.finish();
}
#[cfg(test)]
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::items_after_statements
)]
mod tests {
use super::*;
fn build_b2(slabs: &[(i32, i32)]) -> Vec<i32> {
let mut buf: Vec<i32> = Vec::new();
for &(top, bot) in slabs {
assert!(top < bot, "slab top must be < bot");
assert!(bot < MAXZDIM, "slab bot must fit below MAXZDIM");
buf.push(top);
buf.push(bot);
}
buf.push(MAXZDIM);
buf.push(MAXZDIM);
buf.resize(buf.len() + 32, 0);
buf
}
fn read_slabs(b2: &[i32]) -> Vec<(i32, i32)> {
let mut out = Vec::new();
let mut i = 0;
while b2[i + 1] < MAXZDIM {
out.push((b2[i], b2[i + 1]));
i += 2;
}
out
}
#[test]
fn delslab_noop_y0_ge_y1() {
let mut b2 = build_b2(&[(10, 20)]);
delslab(&mut b2, 15, 15);
assert_eq!(read_slabs(&b2), [(10, 20)]);
delslab(&mut b2, 20, 10);
assert_eq!(read_slabs(&b2), [(10, 20)]);
}
#[test]
fn delslab_split_inside_one_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 15, 20);
assert_eq!(read_slabs(&b2), [(10, 15), (20, 30)]);
}
#[test]
fn delslab_shrink_bot_of_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 20, 30);
assert_eq!(read_slabs(&b2), [(10, 20)]);
}
#[test]
fn delslab_shrink_top_of_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 5, 15);
assert_eq!(read_slabs(&b2), [(15, 30)]);
}
#[test]
fn delslab_carve_full_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 5, 35);
assert_eq!(read_slabs(&b2), Vec::<(i32, i32)>::new());
}
#[test]
fn delslab_in_air_noop() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 0, 8);
assert_eq!(read_slabs(&b2), [(10, 30)]);
delslab(&mut b2, 35, 50);
assert_eq!(read_slabs(&b2), [(10, 30)]);
}
#[test]
fn delslab_span_two_slabs_carve_middle() {
let mut b2 = build_b2(&[(10, 30), (50, 70)]);
delslab(&mut b2, 20, 60);
assert_eq!(read_slabs(&b2), [(10, 20), (60, 70)]);
}
#[test]
fn delslab_carve_two_full_slabs_keep_third() {
let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
delslab(&mut b2, 5, 45);
assert_eq!(read_slabs(&b2), [(50, 60)]);
}
#[test]
fn delslab_y1_clamped_to_maxzdim_minus_1() {
let mut b2 = build_b2(&[(10, 200)]);
delslab(&mut b2, 100, MAXZDIM);
assert_eq!(read_slabs(&b2), [(10, 100)]);
}
#[test]
fn delslab_carve_top_edge_of_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 5, 10);
assert_eq!(read_slabs(&b2), [(10, 30)]);
}
#[test]
fn delslab_carve_bot_edge_of_slab() {
let mut b2 = build_b2(&[(10, 30)]);
delslab(&mut b2, 30, 35);
assert_eq!(read_slabs(&b2), [(10, 30)]);
}
#[test]
fn delslab_carve_exact_full_slab_keeps_neighbors() {
let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
delslab(&mut b2, 30, 40);
assert_eq!(read_slabs(&b2), [(10, 20), (50, 60)]);
}
#[test]
fn insslab_noop_y0_ge_y1() {
let mut b2 = build_b2(&[(10, 20)]);
insslab(&mut b2, 15, 15);
assert_eq!(read_slabs(&b2), [(10, 20)]);
insslab(&mut b2, 20, 10);
assert_eq!(read_slabs(&b2), [(10, 20)]);
}
#[test]
fn insslab_into_pure_air() {
let mut b2 = build_b2(&[]);
insslab(&mut b2, 10, 30);
assert_eq!(read_slabs(&b2), [(10, 30)]);
}
#[test]
fn insslab_into_air_gap_above_slab() {
let mut b2 = build_b2(&[(50, 70)]);
insslab(&mut b2, 10, 30);
assert_eq!(read_slabs(&b2), [(10, 30), (50, 70)]);
}
#[test]
fn insslab_into_air_gap_between_slabs() {
let mut b2 = build_b2(&[(10, 20), (60, 70)]);
insslab(&mut b2, 30, 50);
assert_eq!(read_slabs(&b2), [(10, 20), (30, 50), (60, 70)]);
}
#[test]
fn insslab_into_air_gap_below_all_slabs() {
let mut b2 = build_b2(&[(10, 20)]);
insslab(&mut b2, 30, 50);
assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
}
#[test]
fn insslab_extend_top_of_slab() {
let mut b2 = build_b2(&[(50, 70)]);
insslab(&mut b2, 30, 60);
assert_eq!(read_slabs(&b2), [(30, 70)]);
}
#[test]
fn insslab_extend_bot_of_slab() {
let mut b2 = build_b2(&[(50, 70)]);
insslab(&mut b2, 60, 80);
assert_eq!(read_slabs(&b2), [(50, 80)]);
}
#[test]
fn insslab_touch_top_merges() {
let mut b2 = build_b2(&[(50, 70)]);
insslab(&mut b2, 30, 50);
assert_eq!(read_slabs(&b2), [(30, 70)]);
}
#[test]
fn insslab_touch_bot_merges() {
let mut b2 = build_b2(&[(50, 70)]);
insslab(&mut b2, 70, 80);
assert_eq!(read_slabs(&b2), [(50, 80)]);
}
#[test]
fn insslab_merge_two_slabs() {
let mut b2 = build_b2(&[(10, 30), (50, 70)]);
insslab(&mut b2, 20, 60);
assert_eq!(read_slabs(&b2), [(10, 70)]);
}
#[test]
fn insslab_engulf_inner_slabs() {
let mut b2 = build_b2(&[(10, 20), (30, 40), (50, 60)]);
insslab(&mut b2, 5, 70);
assert_eq!(read_slabs(&b2), [(5, 70)]);
}
#[test]
fn insslab_engulf_then_keep_lower() {
let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
insslab(&mut b2, 5, 50);
assert_eq!(read_slabs(&b2), [(5, 50), (60, 80)]);
}
#[test]
fn insslab_engulf_then_merge_lower() {
let mut b2 = build_b2(&[(10, 20), (30, 40), (60, 80)]);
insslab(&mut b2, 5, 60);
assert_eq!(read_slabs(&b2), [(5, 80)]);
}
#[test]
fn insslab_chain_of_touching_inserts() {
let mut b2 = build_b2(&[]);
insslab(&mut b2, 10, 20);
insslab(&mut b2, 20, 30);
insslab(&mut b2, 30, 40);
assert_eq!(read_slabs(&b2), [(10, 40)]);
}
#[test]
fn insslab_carve_then_insert_round_trip() {
let original = [(10, 50)];
let mut b2 = build_b2(&original);
delslab(&mut b2, 20, 30);
assert_eq!(read_slabs(&b2), [(10, 20), (30, 50)]);
insslab(&mut b2, 20, 30);
assert_eq!(read_slabs(&b2), original);
}
#[test]
fn insslab_into_sentinel_only_buffer_with_z_advance() {
let mut b2 = build_b2(&[(10, 20)]);
insslab(&mut b2, 100, 150);
assert_eq!(read_slabs(&b2), [(10, 20), (100, 150)]);
}
fn read_uind(uind: &[i32]) -> Vec<(i32, i32)> {
let mut out = Vec::new();
let mut i = 0;
while uind[i + 1] < MAXZDIM {
out.push((uind[i], uind[i + 1]));
i += 2;
}
out.push((uind[i], uind[i + 1]));
out
}
#[test]
fn expandrle_single_slab_fully_solid_column() {
let z1c = u8::try_from(MAXZDIM - 1).expect("MAXZDIM-1 fits in u8");
let mut slab = vec![0u8, 0, z1c, 0];
slab.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
let mut uind = vec![0i32; 16];
expandrle(&slab, &mut uind);
assert_eq!(uind[0], 0);
assert_eq!(uind[1], MAXZDIM);
}
#[test]
fn expandrle_single_slab_partial_floor() {
let slab = [0u8, 64, 66, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
let mut uind = vec![0i32; 16];
expandrle(&slab, &mut uind);
assert_eq!(uind[0], 64);
assert_eq!(uind[1], MAXZDIM);
}
#[test]
fn expandrle_two_slabs_with_cave() {
let slab = [
2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
let mut uind = vec![0i32; 16];
expandrle(&slab, &mut uind);
assert_eq!(uind[0], 10);
assert_eq!(uind[1], 30);
assert_eq!(uind[2], 50);
assert_eq!(uind[3], MAXZDIM);
}
#[test]
fn expandrle_skips_degenerate_slab_with_no_ceiling_gap() {
let slab = [
2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 20, 22, 20, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0, 0, ];
let mut uind = vec![0i32; 16];
expandrle(&slab, &mut uind);
assert_eq!(uind[0], 10);
assert_eq!(uind[1], MAXZDIM);
}
#[test]
fn expandrle_round_trips_through_b2_helpers() {
let slab = [
2u8, 10, 10, 0, 0xaa, 0, 0, 0, 0, 50, 52, 30, 0xbb, 0, 0, 0, 0xcc, 0, 0, 0, 0xdd, 0, 0,
0,
];
let mut uind = vec![0i32; 16];
expandrle(&slab, &mut uind);
let runs = read_uind(&uind[..4]);
assert_eq!(runs, [(10, 30), (50, MAXZDIM)]);
}
fn all_air_neighbor() -> Vec<i32> {
let mut buf = vec![MAXZDIM, MAXZDIM];
buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
buf
}
fn b2_from_runs(runs: &[(i32, i32)]) -> Vec<i32> {
let mut buf = Vec::new();
for &(top, bot) in runs {
buf.push(top);
buf.push(bot);
}
buf.push(MAXZDIM);
buf.push(MAXZDIM);
buf.resize(buf.len() + MAXZDIM as usize, MAXZDIM);
buf
}
#[test]
fn build_color_table_single_slab_one_floor_voxel() {
let slab = [0u8, 10, 10, 0, 0xa1, 0xa2, 0xa3, 0xa4];
let table = build_color_table(&slab);
assert_eq!(table.len(), 2);
assert_eq!(table[0].z_start, 10);
assert_eq!(table[0].z_end, 11);
assert_eq!(table[0].colors, &[0xa1, 0xa2, 0xa3, 0xa4]);
assert_eq!(table[1].z_start, MAXZDIM);
assert_eq!(table[1].z_end, MAXZDIM);
}
#[test]
fn build_color_table_two_slabs_with_ceiling() {
let slab = [
4u8, 10, 10, 0, 0xf0, 0xf0, 0xf0, 0xf0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1, 0u8, 50, 52, 30, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, ];
let table = build_color_table(&slab);
assert_eq!(table.len(), 4);
assert_eq!(table[0].z_start, 10);
assert_eq!(table[0].z_end, 11);
assert_eq!(table[0].colors.len(), 4);
assert_eq!(table[1].z_start, 28);
assert_eq!(table[1].z_end, 30);
assert_eq!(
table[1].colors,
&[0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1]
);
assert_eq!(table[2].z_start, 50);
assert_eq!(table[2].z_end, 53);
assert_eq!(table[2].colors.len(), 12);
assert_eq!(table[3].z_start, MAXZDIM);
}
#[test]
fn compilerle_round_trip_single_slab_solid_to_maxzdim() {
let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
for z in 10..MAXZDIM {
slab.extend_from_slice(&[z as u8, (z + 1) as u8, (z + 2) as u8, 0]);
}
let mut b2 = vec![0i32; (MAXZDIM as usize) + 4];
expandrle(&slab, &mut b2);
assert_eq!(b2[0], 10);
assert_eq!(b2[1], MAXZDIM);
let n_air = all_air_neighbor();
let mut cbuf = vec![0u8; 1028];
let mut colfunc_called = 0;
let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
colfunc_called += 1;
0
};
let written = compilerle(
&b2,
&n_air,
&n_air,
&n_air,
&n_air,
&mut cbuf,
&slab,
0,
0,
&mut colfunc,
);
assert_eq!(colfunc_called, 0, "all colors should come from tbuf2");
assert_eq!(written, slab.len());
assert_eq!(&cbuf[..written], &slab[..]);
let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
expandrle(&cbuf[..written], &mut b2_round);
assert_eq!(b2_round[0], 10);
assert_eq!(b2_round[1], MAXZDIM);
}
#[test]
fn compilerle_round_trip_two_solid_runs_with_cave() {
let dummy = vec![0u8, 0, (MAXZDIM - 1) as u8, 0];
let mut dummy_full = dummy;
dummy_full.extend(std::iter::repeat_n(0u8, (MAXZDIM as usize) * 4));
let n_air = all_air_neighbor();
let b2 = b2_from_runs(&[(10, 30), (50, MAXZDIM)]);
let mut seed = vec![0u8; 1028];
let mut colfunc = |_x: i32, _y: i32, z: i32| -> i32 { z };
let seed_len = compilerle(
&b2,
&n_air,
&n_air,
&n_air,
&n_air,
&mut seed,
&dummy_full,
0,
0,
&mut colfunc,
);
seed.truncate(seed_len);
let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
expandrle(&seed, &mut b2_round);
assert_eq!(b2_round[0], 10);
assert_eq!(b2_round[1], 30);
assert_eq!(b2_round[2], 50);
assert_eq!(b2_round[3], MAXZDIM);
let mut cbuf = vec![0u8; 1028];
let mut never_called = 0;
let mut colfunc2 = |_x: i32, _y: i32, _z: i32| -> i32 {
never_called += 1;
0
};
let written = compilerle(
&b2,
&n_air,
&n_air,
&n_air,
&n_air,
&mut cbuf,
&seed,
0,
0,
&mut colfunc2,
);
assert_eq!(never_called, 0, "second pass needs no colfunc");
assert_eq!(written, seed_len);
assert_eq!(&cbuf[..written], &seed[..]);
}
#[test]
fn compilerle_buried_voxel_optimization_with_all_solid_neighbors() {
let b2 = b2_from_runs(&[(10, MAXZDIM)]);
let n_solid = b2_from_runs(&[(0, MAXZDIM)]);
let mut slab = vec![0u8, 10, (MAXZDIM - 1) as u8, 0];
for z in 10..MAXZDIM {
slab.extend_from_slice(&[z as u8, 0, 0, 0]);
}
let mut cbuf = vec![0u8; 1028];
let mut colfunc_called = 0;
let mut colfunc = |_x: i32, _y: i32, _z: i32| -> i32 {
colfunc_called += 1;
0
};
let written = compilerle(
&b2,
&n_solid,
&n_solid,
&n_solid,
&n_solid,
&mut cbuf,
&slab,
0,
0,
&mut colfunc,
);
assert_eq!(colfunc_called, 0, "tbuf2 should cover every voxel");
assert_eq!(written, 8);
assert_eq!(cbuf[0], 0); assert_eq!(cbuf[1], 10); assert_eq!(cbuf[2], 10); assert_eq!(cbuf[3], 0); let mut b2_round = vec![0i32; (MAXZDIM as usize) + 4];
expandrle(&cbuf[..written], &mut b2_round);
assert_eq!(b2_round[0], 10);
assert_eq!(b2_round[1], MAXZDIM);
}
fn build_1x1_min_solid_vxl() -> Vxl {
let column = vec![0u8, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
let column_offset = vec![0u32, column.len() as u32].into_boxed_slice();
Vxl {
vsid: 1,
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: column.into_boxed_slice(),
column_offset,
mip_base_offsets: Box::new([0, 2]),
vbit: Box::new([]),
vbiti: 0,
}
}
#[test]
fn scum2_no_edit_round_trip_1x1_minimal_column() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
let mut ctx = ScumCtx::new(&mut vxl);
let _b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
ctx.finish();
let column = vxl.column_data(0);
let mut b2_after = vec![0i32; SCPITCH];
expandrle(column, &mut b2_after);
assert_eq!(b2_after[0], 0);
assert_eq!(b2_after[1], MAXZDIM);
}
#[test]
fn scum2_carve_edit_1x1_creates_air_gap() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
let mut ctx = ScumCtx::new(&mut vxl);
ctx.set_colfunc(|_x, _y, _z| 0x80_60_40_20u32 as i32);
{
let b2 = ctx.scum2(0, 0).expect("column 0,0 in bounds");
delslab(b2, 50, 100);
}
ctx.finish();
let column = vxl.column_data(0);
let mut b2_after = vec![0i32; SCPITCH];
expandrle(column, &mut b2_after);
assert_eq!(b2_after[0], 0);
assert_eq!(b2_after[1], 50);
assert_eq!(b2_after[2], 100);
assert_eq!(b2_after[3], MAXZDIM);
}
fn build_4x4_min_solid_vxl() -> Vxl {
const COL: [u8; 8] = [0, 0, 0, 0, 0xff, 0x80, 0x40, 0x20];
let mut data = Vec::with_capacity(16 * 8);
let mut offsets = Vec::with_capacity(17);
for i in 0..16 {
offsets.push((i * 8) as u32);
data.extend_from_slice(&COL);
}
offsets.push((16 * 8) as u32);
Vxl {
vsid: 4,
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: offsets.into_boxed_slice(),
mip_base_offsets: Box::new([0, 17]),
vbit: Box::new([]),
vbiti: 0,
}
}
#[test]
fn scum2_batch_edits_multiple_columns_same_row() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
let mut ctx = ScumCtx::new(&mut vxl);
ctx.set_colfunc(|_x, _y, _z| 0);
{
let b2 = ctx.scum2(1, 2).unwrap();
delslab(b2, 50, 100);
}
{
let b2 = ctx.scum2(2, 2).unwrap();
delslab(b2, 50, 100);
}
ctx.finish();
for x in [1, 2] {
let idx = 2 * 4 + x;
let mut b2_after = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2_after);
assert_eq!(b2_after[0], 0);
assert_eq!(b2_after[1], 50);
assert_eq!(b2_after[2], 100);
assert_eq!(b2_after[3], MAXZDIM);
}
for x in [0, 3] {
let idx = 2 * 4 + x;
let mut b2_after = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2_after);
assert_eq!(b2_after[0], 0);
assert_eq!(b2_after[1], MAXZDIM);
}
}
#[test]
fn scum2_batch_edits_across_rows() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
let mut ctx = ScumCtx::new(&mut vxl);
ctx.set_colfunc(|_x, _y, _z| 0);
{
let b2 = ctx.scum2(1, 1).unwrap();
delslab(b2, 60, 80);
}
{
let b2 = ctx.scum2(1, 2).unwrap();
delslab(b2, 60, 80);
}
ctx.finish();
for y in [1, 2] {
let idx = y * 4 + 1;
let mut b2_after = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2_after);
assert_eq!(b2_after[0], 0);
assert_eq!(b2_after[1], 60);
assert_eq!(b2_after[2], 80);
assert_eq!(b2_after[3], MAXZDIM);
}
}
#[test]
fn scum2_finish_without_any_edit_is_noop() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
let original = vxl.column_data(0).to_vec();
let ctx = ScumCtx::new(&mut vxl);
ctx.finish();
assert_eq!(vxl.column_data(0), &original[..]);
}
#[test]
fn scum2_returns_none_for_out_of_bounds() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
let mut ctx = ScumCtx::new(&mut vxl);
assert!(ctx.scum2(-1, 0).is_none());
assert!(ctx.scum2(0, -1).is_none());
assert!(ctx.scum2(1, 0).is_none());
assert!(ctx.scum2(0, 1).is_none());
ctx.finish();
}
#[test]
fn set_spans_empty_is_noop() {
let mut vxl = build_1x1_min_solid_vxl();
let original = vxl.column_data(0).to_vec();
set_spans(&mut vxl, &[], None);
assert_eq!(vxl.column_data(0), &original[..]);
}
#[test]
fn set_spans_single_carve_creates_air_gap() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_spans(
&mut vxl,
&[Vspan {
x: 0,
y: 0,
z0: 50,
z1: 99,
}],
None,
);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(0), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 50);
assert_eq!(b2[2], 100);
assert_eq!(b2[3], MAXZDIM);
}
#[test]
fn set_spans_multi_span_same_column_accumulates() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_spans(
&mut vxl,
&[
Vspan {
x: 0,
y: 0,
z0: 30,
z1: 49,
},
Vspan {
x: 0,
y: 0,
z0: 100,
z1: 119,
},
],
None,
);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(0), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 30);
assert_eq!(b2[2], 50);
assert_eq!(b2[3], 100);
assert_eq!(b2[4], 120);
assert_eq!(b2[5], MAXZDIM);
}
#[test]
fn set_spans_insert_color_fills_air() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_spans(
&mut vxl,
&[Vspan {
x: 0,
y: 0,
z0: 50,
z1: 99,
}],
None,
);
const FILL: u32 = 0x80_aa_bb_cc;
set_spans(
&mut vxl,
&[Vspan {
x: 0,
y: 0,
z0: 60,
z1: 79,
}],
Some(FILL),
);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(0), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 50);
assert_eq!(b2[2], 60);
assert_eq!(b2[3], 80);
assert_eq!(b2[4], 100);
assert_eq!(b2[5], MAXZDIM);
}
#[test]
fn set_spans_skips_out_of_bounds_silently() {
let mut vxl = build_1x1_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_spans(
&mut vxl,
&[Vspan {
x: 7,
y: 9,
z0: 50,
z1: 99,
}],
None,
);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(0), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], MAXZDIM);
}
#[test]
fn set_spans_with_colfunc_z_dependent_colour() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
let carve_spans: Vec<Vspan> = (0..4)
.flat_map(|y| {
(0..4).map(move |x| Vspan {
x,
y,
z0: 50,
z1: 99,
})
})
.collect();
set_spans(&mut vxl, &carve_spans, None);
set_spans_with_colfunc(
&mut vxl,
&[Vspan {
x: 1,
y: 1,
z0: 60,
z1: 79,
}],
SpanOp::Insert,
|_x, _y, z| (0x80ff_ff00u32 as i32) | z,
);
let idx = 4 + 1; let column = vxl.column_data(idx);
let mut v = 0usize;
let mut found = false;
loop {
let nextptr = column[v];
let z1 = column[v + 1];
if z1 == 60 {
let z1c = column[v + 2];
assert_eq!(z1c, 79, "z1c");
let n_voxels = usize::from(z1c) - usize::from(z1) + 1;
for i in 0..n_voxels {
let off = v + 4 + i * 4;
let c = u32::from_le_bytes([
column[off],
column[off + 1],
column[off + 2],
column[off + 3],
]);
let z = u32::from(z1) + (i as u32);
assert_eq!(
c,
0x80ff_ff00 | z,
"z={z}: expected colour {:#010x}, got {:#010x}",
0x80ff_ff00 | z,
c
);
}
found = true;
break;
}
if nextptr == 0 {
break;
}
v += usize::from(nextptr) * 4;
}
assert!(found, "did not find a slab with z1=60");
}
#[test]
fn set_cube_carves_single_voxel() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_cube(&mut vxl, 1, 1, 100, None);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4 + 1), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 100);
assert_eq!(b2[2], 101);
assert_eq!(b2[3], MAXZDIM);
}
#[test]
fn set_cube_skips_oob() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_cube(&mut vxl, -1, 1, 100, None);
set_cube(&mut vxl, 5, 1, 100, None);
set_cube(&mut vxl, 1, 1, 256, None);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4 + 1), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], MAXZDIM);
}
#[test]
fn set_rect_carves_aabb() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
set_rect(&mut vxl, [1, 1, 50], [2, 2, 99], None);
for y in 1..=2 {
for x in 1..=2 {
let idx = (y * 4 + x) as usize;
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 0, "col ({x},{y})");
assert_eq!(b2[1], 50, "col ({x},{y})");
assert_eq!(b2[2], 100, "col ({x},{y})");
assert_eq!(b2[3], MAXZDIM, "col ({x},{y})");
}
}
for &(x, y) in &[(0, 0), (3, 3)] {
let idx = (y * 4 + x) as usize;
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], MAXZDIM);
}
}
#[test]
fn set_rect_clamps_to_world() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
set_rect(&mut vxl, [-10, -10, -10], [100, 100, 1000], None);
for idx in 0..16 {
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(idx), &mut b2);
assert_eq!(b2[0], 255, "col {idx}");
assert_eq!(b2[1], MAXZDIM, "col {idx}");
}
}
#[test]
fn set_sphere_carves_centred_sphere() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
set_sphere(&mut vxl, [1, 1, 128], 1, None);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4 + 1), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 127);
assert_eq!(b2[2], 130);
assert_eq!(b2[3], MAXZDIM);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 128);
assert_eq!(b2[2], 129);
assert_eq!(b2[3], MAXZDIM);
}
#[test]
fn set_sphere_radius_zero_is_single_voxel() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(4096);
set_sphere(&mut vxl, [1, 1, 100], 0, None);
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4 + 1), &mut b2);
assert_eq!(b2[0], 0);
assert_eq!(b2[1], 100);
assert_eq!(b2[2], 101);
assert_eq!(b2[3], MAXZDIM);
}
#[test]
fn set_sphere_with_colfunc_position_dependent_color() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
set_rect(&mut vxl, [0, 0, 50], [3, 3, 199], None);
set_sphere_with_colfunc(&mut vxl, [1, 1, 128], 2, SpanOp::Insert, |_, _, z| {
(0x80ff_ff00u32 as i32) | z
});
let mut b2 = vec![0i32; SCPITCH];
expandrle(vxl.column_data(4 + 1), &mut b2);
assert_eq!(b2[0], 0, "b2 first run top");
assert_eq!(b2[1], 50, "b2 first run bot");
assert_eq!(b2[2], 126, "b2 sphere run top");
assert_eq!(b2[3], 131, "b2 sphere run bot");
assert_eq!(b2[4], 200, "b2 third run top");
assert_eq!(b2[5], MAXZDIM, "b2 third run bot");
let column = vxl.column_data(4 + 1).to_vec();
let mut v = 0usize;
let mut top_color = None;
loop {
let nextptr = column[v];
let z1 = column[v + 1];
if z1 == 126 {
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(0x80ff_ff7e),
"exposed voxel at z=126 should have colfunc-derived colour"
);
}
#[test]
fn set_spans_4x4_batch_carves_each_listed_column() {
let mut vxl = build_4x4_min_solid_vxl();
vxl.reserve_edit_capacity(8192);
let spans: Vec<Vspan> = (0..4)
.flat_map(|y| {
(0..4).map(move |x| Vspan {
x,
y,
z0: 50,
z1: 99,
})
})
.collect();
set_spans(&mut vxl, &spans, None);
for idx in 0..16 {
let mut b2 = vec![0i32; SCPITCH];
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}");
}
}
}