#[must_use]
pub fn camera_column_air_gap(
column: &[u8],
cz: i32,
treat_z_max_as_air: bool,
) -> Option<(i32, i32, usize)> {
if column.len() < 4 {
return None;
}
let first_z1 = i32::from(column[1]);
if cz < first_z1 {
return Some((0, first_z1, 0));
}
let mut pos = 0usize;
let mut prev_floor_z1c = first_z1; loop {
let nextptr = column[pos];
if nextptr == 0 {
if treat_z_max_as_air {
let last_z1 = i32::from(column[pos + 1]);
if last_z1 == 0xff {
return Some((prev_floor_z1c, last_z1, pos));
}
}
return None;
}
prev_floor_z1c = i32::from(column[pos + 2]) + 1;
pos = pos.checked_add(usize::from(nextptr) * 4)?;
if pos.checked_add(4)? > column.len() {
return None;
}
let z1 = i32::from(column[pos + 1]);
if cz < z1 {
let z0 = i32::from(column[pos + 3]);
if cz < z0 {
return None;
}
return Some((z0, z1, pos));
}
}
}
#[must_use]
pub fn camera_chunk_air_gap(
grid: crate::grid_view::GridView<'_>,
prelude: &crate::opticast_prelude::OpticastPrelude,
treat_z_max_as_air: bool,
) -> Option<(i32, i32, usize, i32)> {
if !prelude.in_bounds_xy {
let chunks_z_signed = grid.chunk_grid.map_or(1, |cg| cg.chunks_z) as i32;
let seed_chz = grid.chunk_grid.map_or(0, |cg| cg.origin_chunk_z);
#[allow(clippy::cast_possible_wrap)]
let world_z_max = chunks_z_signed * grid.chunk_size_z as i32 - 1;
return Some((0, world_z_max, 0, seed_chz));
}
let chunks_z = grid.chunk_grid.map_or(1, |cg| cg.chunks_z) as i32;
let origin_chunk_z = grid.chunk_grid.map_or(0, |cg| cg.origin_chunk_z);
let max_chz = origin_chunk_z + chunks_z - 1;
let camera_chz = prelude.camera_chunk_idx[2];
#[allow(clippy::cast_possible_wrap)]
let chunk_size_z_signed = grid.chunk_size_z as i32;
let mut chz = camera_chz.clamp(origin_chunk_z, max_chz);
let mut z0_world: Option<i32> = None;
loop {
let chunk = grid.chunk_at_xyz([
prelude.camera_chunk_idx[0],
prelude.camera_chunk_idx[1],
chz,
])?;
#[allow(clippy::cast_sign_loss)]
let column_idx_in_chunk = (prelude.camera_local_xyz[1] as u32)
.wrapping_mul(chunk.chunk_size_xy)
.wrapping_add(prelude.camera_local_xyz[0] as u32);
let column = crate::opticast::camera_column_slice(
chunk.slab_buf,
chunk.column_offsets,
column_idx_in_chunk,
)?;
let cz_local = if chz == camera_chz {
prelude.camera_local_xyz[2]
} else if chz > camera_chz {
-1
} else {
chunk_size_z_signed
};
let (local_z0, local_z1, vptr) =
camera_column_air_gap(column, cz_local, treat_z_max_as_air)?;
let chunk_world_z_base = chz * chunk_size_z_signed;
let z0w = *z0_world.get_or_insert(local_z0 + chunk_world_z_base);
if local_z1 == 0xff && vptr == 0 && treat_z_max_as_air && chz < max_chz {
chz += 1;
continue;
}
return Some((z0w, local_z1 + chunk_world_z_base, vptr, chz));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn single_slab_5_15() -> Vec<u8> {
vec![0, 5, 14, 0]
}
fn two_slabs_air_at_20_30() -> Vec<u8> {
let mut col = Vec::new();
col.extend_from_slice(&[6, 10, 14, 0]);
col.resize(col.len() + 5 * 4, 0xab);
col.extend_from_slice(&[0, 30, 39, 20]);
col.resize(col.len() + 10 * 4, 0xcd);
col
}
#[test]
fn camera_above_first_slab_returns_zero_to_z1() {
let col = single_slab_5_15();
assert_eq!(camera_column_air_gap(&col, 0, false), Some((0, 5, 0)));
assert_eq!(camera_column_air_gap(&col, 4, false), Some((0, 5, 0)));
}
#[test]
fn camera_inside_solid_returns_none() {
let col = single_slab_5_15();
assert_eq!(camera_column_air_gap(&col, 10, false), None);
assert_eq!(camera_column_air_gap(&col, 100, false), None);
}
#[test]
fn camera_above_first_slab_in_two_slab_column() {
let col = two_slabs_air_at_20_30();
assert_eq!(camera_column_air_gap(&col, 5, false), Some((0, 10, 0)));
}
#[test]
fn camera_in_air_gap_between_slabs() {
let col = two_slabs_air_at_20_30();
assert_eq!(camera_column_air_gap(&col, 25, false), Some((20, 30, 24)));
assert_eq!(camera_column_air_gap(&col, 20, false), Some((20, 30, 24)));
assert_eq!(camera_column_air_gap(&col, 29, false), Some((20, 30, 24)));
}
#[test]
fn camera_in_first_slabs_hidden_interior_returns_none() {
let col = two_slabs_air_at_20_30();
assert_eq!(camera_column_air_gap(&col, 12, false), None);
}
#[test]
fn camera_in_last_slab_returns_none() {
let col = two_slabs_air_at_20_30();
assert_eq!(camera_column_air_gap(&col, 35, false), None);
assert_eq!(camera_column_air_gap(&col, 1000, false), None);
}
#[test]
fn malformed_too_short_returns_none() {
assert_eq!(camera_column_air_gap(&[1, 2, 3], 10, false), None);
}
#[test]
fn malformed_nextptr_overruns_returns_none() {
let col = vec![99, 10, 14, 0];
assert_eq!(camera_column_air_gap(&col, 100, false), None);
}
#[test]
fn camera_chunk_air_gap_inbounds_matches_column_path() {
let col = two_slabs_air_at_20_30();
let column_offsets = [0u32, col.len() as u32];
let mip_base = [0usize, column_offsets.len()];
let grid = crate::grid_view::GridView::from_parts(1, &col, &column_offsets, &mip_base);
let prelude = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [0, 0, 25], column_index: 0,
pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: 0,
cy: 0,
in_bounds_xy: true,
camera_chunk_idx: [0, 0, 0],
camera_local_xyz: [0, 0, 25],
};
let chunk_path = camera_chunk_air_gap(grid, &prelude, false);
let column_path = camera_column_air_gap(&col, 25, false);
assert_eq!(chunk_path.map(|(z0, z1, vp, _)| (z0, z1, vp)), column_path);
assert_eq!(chunk_path, Some((20, 30, 24, 0)));
}
#[test]
fn camera_chunk_air_gap_oob_synthesises_bedrock_seed() {
let mip_base = [0usize, 0];
let grid = crate::grid_view::GridView::from_parts(1, &[], &[], &mip_base);
let prelude = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [-5, 0, 30],
column_index: u32::MAX, pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: -5,
cy: 0,
in_bounds_xy: false,
camera_chunk_idx: [0, 0, 0],
camera_local_xyz: [-5, 0, 30],
};
assert_eq!(
camera_chunk_air_gap(grid, &prelude, true),
Some((0, 255, 0, 0))
);
}
#[test]
fn camera_chunk_air_gap_inbounds_in_solid_returns_none() {
let col = single_slab_5_15();
let column_offsets = [0u32, col.len() as u32];
let mip_base = [0usize, column_offsets.len()];
let grid = crate::grid_view::GridView::from_parts(1, &col, &column_offsets, &mip_base);
let prelude = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [0, 0, 10], column_index: 0,
pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: 0,
cy: 0,
in_bounds_xy: true,
camera_chunk_idx: [0, 0, 0],
camera_local_xyz: [0, 0, 10],
};
assert_eq!(camera_chunk_air_gap(grid, &prelude, false), None);
}
#[test]
fn camera_chunk_air_gap_cross_chunk_lookdown_to_chz1_floor() {
use crate::grid_view::{ChunkGrid, GridView};
let col_chz0: Vec<u8> = vec![0, 0xff, 0xff, 0];
let mut col_chz1 = Vec::new();
col_chz1.extend_from_slice(&[2, 50, 50, 0]); col_chz1.extend_from_slice(&[0xcc; 4]); col_chz1.extend_from_slice(&[0, 0xff, 0xff, 51]);
let cols0 = [0u32, col_chz0.len() as u32];
let cols1 = [0u32, col_chz1.len() as u32];
let mips0 = [0usize, cols0.len()];
let mips1 = [0usize, cols1.len()];
let c0 = GridView::from_parts(1, &col_chz0, &cols0, &mips0);
let c1 = GridView::from_parts(1, &col_chz1, &cols1, &mips1);
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 parent = GridView::from_chunk_grid(&cg, 1);
let prelude = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [0, 0, 100],
column_index: 0,
pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: 0,
cy: 0,
in_bounds_xy: true,
camera_chunk_idx: [0, 0, 0],
camera_local_xyz: [0, 0, 100],
};
assert_eq!(
camera_chunk_air_gap(parent, &prelude, false),
Some((0, 0xff, 0, 0))
);
assert_eq!(
camera_chunk_air_gap(parent, &prelude, true),
Some((0, 306, 0, 1))
);
}
#[test]
fn below_bedrock_synthesises_air_when_flag_set() {
let mut col = Vec::new();
col.extend_from_slice(&[2, 200, 200, 0]); col.extend_from_slice(&[0xff; 4]); col.extend_from_slice(&[0, 255, 255, 201]); col.extend_from_slice(&[0xff; 4]); assert_eq!(camera_column_air_gap(&col, 261, false), None);
assert_eq!(camera_column_air_gap(&col, 261, true), Some((201, 255, 8)));
}
#[test]
fn camera_chunk_air_gap_dispatches_by_camera_chz() {
use crate::grid_view::{ChunkGrid, GridView};
let mut col_chz0 = Vec::new();
col_chz0.extend_from_slice(&[2, 50, 50, 0]); col_chz0.extend_from_slice(&[0xaa; 4]); let mut col_chz1 = Vec::new();
col_chz1.extend_from_slice(&[2, 50, 50, 0]); col_chz1.extend_from_slice(&[0xbb; 4]);
let cols0 = [0u32, col_chz0.len() as u32];
let cols1 = [0u32, col_chz1.len() as u32];
let mips = [0usize, cols0.len()];
let c0 = GridView::from_parts(1, &col_chz0, &cols0, &mips);
let c1 = GridView::from_parts(1, &col_chz1, &cols1, &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 parent = GridView::from_chunk_grid(&cg, 1);
let prelude_chz0 = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [0, 0, 20],
column_index: 0,
pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: 0,
cy: 0,
in_bounds_xy: true,
camera_chunk_idx: [0, 0, 0],
camera_local_xyz: [0, 0, 20],
};
let gap0 = camera_chunk_air_gap(parent, &prelude_chz0, false);
assert_eq!(gap0, Some((0, 50, 0, 0)));
let prelude_chz1 = crate::opticast_prelude::OpticastPrelude {
forward_z_sign: 1,
li_pos: [0, 0, 280],
column_index: 0,
pos_xfrac: [1.0, 0.0],
pos_yfrac: [1.0, 0.0],
pos_z: 0,
y_lookup: Vec::new(),
mip_levels: 1,
x_mip: 0,
max_scan_dist: 0,
cx: 0,
cy: 0,
in_bounds_xy: true,
camera_chunk_idx: [0, 0, 1],
camera_local_xyz: [0, 0, 24],
};
let gap1 = camera_chunk_air_gap(parent, &prelude_chz1, false);
assert_eq!(gap1, Some((256, 306, 0, 1)));
let cv0 = parent.chunk_at_xyz([0, 0, 0]).unwrap();
let cv1 = parent.chunk_at_xyz([0, 0, 1]).unwrap();
assert_eq!(cv0.slab_buf, &col_chz0[..]);
assert_eq!(cv1.slab_buf, &col_chz1[..]);
}
}