use super::{Volume3D, VoxelDistGrad};
#[inline]
fn child_span(i: usize, next: usize, prev: usize) -> (usize, usize) {
debug_assert!(prev <= 1 || 2 * next <= prev);
let start = (i * 2).min(prev.saturating_sub(1));
let end = if i + 1 >= next {
prev
} else {
((i + 1) * 2).min(prev)
};
(start, end.max(start + 1))
}
pub fn generate_mip_chain(volume: &Volume3D<f32>) -> Vec<Vec<f32>> {
let mut mips: Vec<Vec<f32>> = Vec::new();
let mut res_chain = vec![volume.resolution];
loop {
let prev = *res_chain.last().unwrap();
let next = [
(prev[0] / 2).max(1),
(prev[1] / 2).max(1),
(prev[2] / 2).max(1),
];
if next == prev {
break;
}
res_chain.push(next);
}
for level in 1..res_chain.len() {
let prev_res = res_chain[level - 1];
let next_res = res_chain[level];
let prev_data: &[f32] = if level == 1 {
&volume.data
} else {
&mips[level - 2]
};
let total = next_res[0] as usize * next_res[1] as usize * next_res[2] as usize;
let mut mip_data = vec![f32::MAX; total];
let prev_sx = prev_res[0] as usize;
let prev_sy = prev_res[1] as usize;
for z in 0..next_res[2] as usize {
let (z0, z1) = child_span(z, next_res[2] as usize, prev_res[2] as usize);
for y in 0..next_res[1] as usize {
let (y0, y1) = child_span(y, next_res[1] as usize, prev_res[1] as usize);
for x in 0..next_res[0] as usize {
let (x0, x1) = child_span(x, next_res[0] as usize, prev_res[0] as usize);
let mut min_val = f32::MAX;
for cz in z0..z1 {
let plane = cz * prev_sx * prev_sy;
for cy in y0..y1 {
let row = plane + cy * prev_sx;
for cx in x0..x1 {
min_val = min_val.min(prev_data[row + cx]);
}
}
}
let out_idx = x
+ y * next_res[0] as usize
+ z * next_res[0] as usize * next_res[1] as usize;
mip_data[out_idx] = min_val;
}
}
}
mips.push(mip_data);
}
mips
}
pub fn generate_mip_chain_distgrad(volume: &Volume3D<VoxelDistGrad>) -> Vec<Vec<VoxelDistGrad>> {
let mut mips: Vec<Vec<VoxelDistGrad>> = Vec::new();
let mut res_chain = vec![volume.resolution];
loop {
let prev = *res_chain.last().unwrap();
let next = [
(prev[0] / 2).max(1),
(prev[1] / 2).max(1),
(prev[2] / 2).max(1),
];
if next == prev {
break;
}
res_chain.push(next);
}
for level in 1..res_chain.len() {
let prev_res = res_chain[level - 1];
let next_res = res_chain[level];
let prev_data: &[VoxelDistGrad] = if level == 1 {
&volume.data
} else {
&mips[level - 2]
};
let total = next_res[0] as usize * next_res[1] as usize * next_res[2] as usize;
let mut mip_data = vec![VoxelDistGrad::default(); total];
let prev_sx = prev_res[0] as usize;
let prev_sy = prev_res[1] as usize;
for z in 0..next_res[2] as usize {
let (z0, z1) = child_span(z, next_res[2] as usize, prev_res[2] as usize);
for y in 0..next_res[1] as usize {
let (y0, y1) = child_span(y, next_res[1] as usize, prev_res[1] as usize);
for x in 0..next_res[0] as usize {
let (x0, x1) = child_span(x, next_res[0] as usize, prev_res[0] as usize);
let mut min_child = prev_data[x0 + y0 * prev_sx + z0 * prev_sx * prev_sy];
for cz in z0..z1 {
let plane = cz * prev_sx * prev_sy;
for cy in y0..y1 {
let row = plane + cy * prev_sx;
for cx in x0..x1 {
let child = prev_data[row + cx];
if child.distance < min_child.distance {
min_child = child;
}
}
}
}
let out_idx = x
+ y * next_res[0] as usize
+ z * next_res[0] as usize * next_res[1] as usize;
mip_data[out_idx] = min_child;
}
}
}
mips.push(mip_data);
}
mips
}
#[cfg(test)]
mod tests {
use super::*;
use glam::Vec3;
#[test]
fn test_mip_chain_dimensions() {
let vol: Volume3D<f32> = Volume3D::new([8, 8, 8], Vec3::splat(-1.0), Vec3::splat(1.0));
let mips = generate_mip_chain(&vol);
assert_eq!(mips.len(), 3); assert_eq!(mips[0].len(), 4 * 4 * 4);
assert_eq!(mips[1].len(), 2 * 2 * 2);
assert_eq!(mips[2].len(), 1);
}
#[test]
fn test_mip_chain_min_downsample() {
let mut vol: Volume3D<f32> = Volume3D::new([4, 4, 4], Vec3::splat(-1.0), Vec3::splat(1.0));
for v in vol.data.iter_mut() {
*v = 10.0;
}
vol.set(0, 0, 0, -1.0);
let mips = generate_mip_chain(&vol);
assert!(
mips[0][0] < 0.0,
"Min downsample should preserve negative distance"
);
}
#[test]
fn test_mip_chain_distgrad() {
let vol: Volume3D<VoxelDistGrad> =
Volume3D::new([4, 4, 4], Vec3::splat(-1.0), Vec3::splat(1.0));
let mips = generate_mip_chain_distgrad(&vol);
assert_eq!(mips.len(), 2); assert_eq!(mips[0].len(), 2 * 2 * 2);
assert_eq!(mips[1].len(), 1);
}
#[test]
fn test_non_power_of_two() {
let vol: Volume3D<f32> = Volume3D::new([6, 6, 6], Vec3::splat(-1.0), Vec3::splat(1.0));
let mips = generate_mip_chain(&vol);
assert_eq!(mips.len(), 2);
assert_eq!(mips[0].len(), 3 * 3 * 3);
assert_eq!(mips[1].len(), 1);
}
}