#![allow(dead_code)]
use crate::mesh::MeshBuffers;
use crate::normals::compute_normals;
#[derive(Debug, Clone)]
pub struct VoxelGrid {
pub data: Vec<bool>,
pub dims: [usize; 3],
pub origin: [f32; 3],
pub voxel_size: f32,
}
impl VoxelGrid {
pub fn new(dims: [usize; 3], origin: [f32; 3], voxel_size: f32) -> Self {
let total = dims[0] * dims[1] * dims[2];
VoxelGrid {
data: vec![false; total],
dims,
origin,
voxel_size,
}
}
#[inline]
fn idx(&self, ix: usize, iy: usize, iz: usize) -> usize {
iz * self.dims[1] * self.dims[0] + iy * self.dims[0] + ix
}
pub fn get(&self, ix: usize, iy: usize, iz: usize) -> bool {
self.data[self.idx(ix, iy, iz)]
}
pub fn set(&mut self, ix: usize, iy: usize, iz: usize, val: bool) {
let i = self.idx(ix, iy, iz);
self.data[i] = val;
}
pub fn world_pos(&self, ix: usize, iy: usize, iz: usize) -> [f32; 3] {
[
self.origin[0] + ix as f32 * self.voxel_size,
self.origin[1] + iy as f32 * self.voxel_size,
self.origin[2] + iz as f32 * self.voxel_size,
]
}
pub fn voxel_count(&self) -> usize {
self.data.iter().filter(|&&v| v).count()
}
pub fn total_cells(&self) -> usize {
self.dims[0] * self.dims[1] * self.dims[2]
}
pub fn density(&self) -> f32 {
let total = self.total_cells();
if total == 0 {
return 0.0;
}
self.voxel_count() as f32 / total as f32
}
}
pub struct VoxelizeParams {
pub resolution: usize,
pub surface_only: bool,
pub padding: f32,
}
impl Default for VoxelizeParams {
fn default() -> Self {
Self {
resolution: 32,
surface_only: false,
padding: 0.05,
}
}
}
#[inline]
fn vec3_sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn vec3_cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[inline]
fn vec3_dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn vec3_len(a: [f32; 3]) -> f32 {
(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
}
pub fn mesh_bounds(mesh: &MeshBuffers) -> ([f32; 3], [f32; 3]) {
if mesh.positions.is_empty() {
return ([0.0; 3], [0.0; 3]);
}
let mut mn = mesh.positions[0];
let mut mx = mesh.positions[0];
for p in &mesh.positions {
mn[0] = mn[0].min(p[0]);
mn[1] = mn[1].min(p[1]);
mn[2] = mn[2].min(p[2]);
mx[0] = mx[0].max(p[0]);
mx[1] = mx[1].max(p[1]);
mx[2] = mx[2].max(p[2]);
}
(mn, mx)
}
fn compute_grid_params(
mn: [f32; 3],
mx: [f32; 3],
params: &VoxelizeParams,
) -> ([f32; 3], f32, [usize; 3]) {
let extents = [mx[0] - mn[0], mx[1] - mn[1], mx[2] - mn[2]];
let longest = extents[0].max(extents[1]).max(extents[2]).max(1e-6);
let voxel_size = longest / params.resolution as f32;
let pad = longest * params.padding;
let origin = [
mn[0] - pad + voxel_size * 0.5,
mn[1] - pad + voxel_size * 0.5,
mn[2] - pad + voxel_size * 0.5,
];
let dims = [
(((extents[0] + 2.0 * pad) / voxel_size).ceil() as usize).max(1),
(((extents[1] + 2.0 * pad) / voxel_size).ceil() as usize).max(1),
(((extents[2] + 2.0 * pad) / voxel_size).ceil() as usize).max(1),
];
(origin, voxel_size, dims)
}
pub fn voxelize_surface(mesh: &MeshBuffers, params: &VoxelizeParams) -> VoxelGrid {
let (mn, mx) = mesh_bounds(mesh);
let (origin, voxel_size, dims) = compute_grid_params(mn, mx, params);
let mut grid = VoxelGrid::new(dims, origin, voxel_size);
let threshold = voxel_size * 0.86;
let faces = mesh.indices.len() / 3;
for f in 0..faces {
let i0 = mesh.indices[f * 3] as usize;
let i1 = mesh.indices[f * 3 + 1] as usize;
let i2 = mesh.indices[f * 3 + 2] as usize;
if i0 >= mesh.positions.len() || i1 >= mesh.positions.len() || i2 >= mesh.positions.len() {
continue;
}
let v0 = mesh.positions[i0];
let v1 = mesh.positions[i1];
let v2 = mesh.positions[i2];
let face_min = [
v0[0].min(v1[0]).min(v2[0]),
v0[1].min(v1[1]).min(v2[1]),
v0[2].min(v1[2]).min(v2[2]),
];
let face_max = [
v0[0].max(v1[0]).max(v2[0]),
v0[1].max(v1[1]).max(v2[1]),
v0[2].max(v1[2]).max(v2[2]),
];
let ix_min = ((face_min[0] - origin[0] - threshold) / voxel_size)
.floor()
.max(0.0) as usize;
let iy_min = ((face_min[1] - origin[1] - threshold) / voxel_size)
.floor()
.max(0.0) as usize;
let iz_min = ((face_min[2] - origin[2] - threshold) / voxel_size)
.floor()
.max(0.0) as usize;
let ix_max = ((face_max[0] - origin[0] + threshold) / voxel_size)
.ceil()
.min((dims[0] - 1) as f32) as usize;
let iy_max = ((face_max[1] - origin[1] + threshold) / voxel_size)
.ceil()
.min((dims[1] - 1) as f32) as usize;
let iz_max = ((face_max[2] - origin[2] + threshold) / voxel_size)
.ceil()
.min((dims[2] - 1) as f32) as usize;
let e1 = vec3_sub(v1, v0);
let e2 = vec3_sub(v2, v0);
let normal = vec3_cross(e1, e2);
let normal_len_sq = vec3_dot(normal, normal);
for iz in iz_min..=iz_max {
for iy in iy_min..=iy_max {
for ix in ix_min..=ix_max {
let centre = grid.world_pos(ix, iy, iz);
if normal_len_sq > 1e-12 {
let to_point = vec3_sub(centre, v0);
let dist_plane = (vec3_dot(normal, to_point) / normal_len_sq.sqrt()).abs();
if dist_plane > threshold {
continue;
}
let w = vec3_sub(centre, v0);
let dot00 = vec3_dot(e2, e2);
let dot01 = vec3_dot(e2, e1);
let dot02 = vec3_dot(e2, w);
let dot11 = vec3_dot(e1, e1);
let dot12 = vec3_dot(e1, w);
let inv_denom = 1.0 / (dot00 * dot11 - dot01 * dot01);
let u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
let v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
let margin = 0.05;
if u >= -margin && v >= -margin && u + v <= 1.0 + margin {
grid.set(ix, iy, iz, true);
}
} else {
let d = vec3_len(vec3_sub(centre, v0));
if d <= threshold {
grid.set(ix, iy, iz, true);
}
}
}
}
}
}
grid
}
fn ray_z_triangle_t(cx: f32, cy: f32, v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) -> Option<f32> {
let e1 = vec3_sub(v1, v0);
let e2 = vec3_sub(v2, v0);
let h = [-e2[1], e2[0], 0.0];
let a = vec3_dot(e1, h);
if a.abs() < 1e-10 {
return None; }
let f = 1.0 / a;
let s = [cx - v0[0], cy - v0[1], -v0[2]];
let u = f * vec3_dot(s, h);
if !(0.0..=1.0).contains(&u) {
return None;
}
let q = vec3_cross(s, e1);
let v = f * q[2]; if v < 0.0 || u + v > 1.0 {
return None;
}
let t = f * (e2[0] * q[0] + e2[1] * q[1] + e2[2] * q[2]);
Some(t + v0[2])
}
pub fn voxelize_solid(mesh: &MeshBuffers, params: &VoxelizeParams) -> VoxelGrid {
let (mn, mx) = mesh_bounds(mesh);
let (origin, voxel_size, dims) = compute_grid_params(mn, mx, params);
let mut grid = VoxelGrid::new(dims, origin, voxel_size);
let faces = mesh.indices.len() / 3;
let mut tris: Vec<([f32; 3], [f32; 3], [f32; 3])> = Vec::with_capacity(faces);
for f in 0..faces {
let i0 = mesh.indices[f * 3] as usize;
let i1 = mesh.indices[f * 3 + 1] as usize;
let i2 = mesh.indices[f * 3 + 2] as usize;
if i0 < mesh.positions.len() && i1 < mesh.positions.len() && i2 < mesh.positions.len() {
tris.push((mesh.positions[i0], mesh.positions[i1], mesh.positions[i2]));
}
}
for iy in 0..dims[1] {
for ix in 0..dims[0] {
let cx = origin[0] + ix as f32 * voxel_size;
let cy = origin[1] + iy as f32 * voxel_size;
let mut hits: Vec<f32> = tris
.iter()
.filter_map(|&(v0, v1, v2)| ray_z_triangle_t(cx, cy, v0, v1, v2))
.collect();
hits.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
hits.dedup_by(|a, b| (*a - *b).abs() < voxel_size * 0.01);
for iz in 0..dims[2] {
let cz = origin[2] + iz as f32 * voxel_size;
let count = hits.iter().filter(|&&hz| hz < cz).count();
if count % 2 == 1 {
grid.set(ix, iy, iz, true);
}
}
}
}
grid
}
pub fn voxelize(mesh: &MeshBuffers, params: &VoxelizeParams) -> VoxelGrid {
if params.surface_only {
voxelize_surface(mesh, params)
} else {
voxelize_solid(mesh, params)
}
}
pub fn voxel_to_mesh(grid: &VoxelGrid) -> MeshBuffers {
let hs = grid.voxel_size * 0.5;
let corners: [[f32; 3]; 8] = [
[-hs, -hs, -hs], [hs, -hs, -hs], [hs, hs, -hs], [-hs, hs, -hs], [-hs, -hs, hs], [hs, -hs, hs], [hs, hs, hs], [-hs, hs, hs], ];
let face_quads: [[usize; 4]; 6] = [
[0, 1, 2, 3], [5, 4, 7, 6], [4, 0, 3, 7], [1, 5, 6, 2], [4, 5, 1, 0], [3, 2, 6, 7], ];
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut uvs: Vec<[f32; 2]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let uv_face = [[0.0f32, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
for iz in 0..grid.dims[2] {
for iy in 0..grid.dims[1] {
for ix in 0..grid.dims[0] {
if !grid.get(ix, iy, iz) {
continue;
}
let centre = grid.world_pos(ix, iy, iz);
for quad in &face_quads {
let base = positions.len() as u32;
for (k, &ci) in quad.iter().enumerate() {
let c = corners[ci];
positions.push([centre[0] + c[0], centre[1] + c[1], centre[2] + c[2]]);
uvs.push(uv_face[k]);
}
indices.extend_from_slice(&[
base,
base + 1,
base + 2,
base,
base + 2,
base + 3,
]);
}
}
}
}
let n_verts = positions.len();
let mut buf = MeshBuffers {
positions,
normals: vec![[0.0, 1.0, 0.0]; n_verts],
tangents: vec![[1.0, 0.0, 0.0, 1.0]; n_verts],
uvs,
indices,
colors: None,
has_suit: false,
};
compute_normals(&mut buf);
buf
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::MeshBuffers;
fn make_triangle_mesh(v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) -> MeshBuffers {
MeshBuffers {
positions: vec![v0, v1, v2],
normals: vec![[0.0, 0.0, 1.0]; 3],
tangents: vec![[1.0, 0.0, 0.0, 1.0]; 3],
uvs: vec![[0.0, 0.0]; 3],
indices: vec![0, 1, 2],
colors: None,
has_suit: false,
}
}
fn make_quad_mesh() -> MeshBuffers {
MeshBuffers {
positions: vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
],
normals: vec![[0.0, 0.0, 1.0]; 4],
tangents: vec![[1.0, 0.0, 0.0, 1.0]; 4],
uvs: vec![[0.0, 0.0]; 4],
indices: vec![0, 1, 2, 0, 2, 3],
colors: None,
has_suit: false,
}
}
#[test]
fn test_voxel_grid_new() {
let grid = VoxelGrid::new([4, 4, 4], [0.0; 3], 1.0);
assert_eq!(grid.total_cells(), 64);
assert_eq!(grid.voxel_count(), 0);
assert!(!grid.get(0, 0, 0));
}
#[test]
fn test_voxel_grid_get_set() {
let mut grid = VoxelGrid::new([3, 3, 3], [0.0; 3], 1.0);
grid.set(1, 2, 0, true);
assert!(grid.get(1, 2, 0));
assert!(!grid.get(0, 0, 0));
assert_eq!(grid.voxel_count(), 1);
}
#[test]
fn test_voxel_grid_world_pos() {
let grid = VoxelGrid::new([5, 5, 5], [1.0, 2.0, 3.0], 0.5);
let wp = grid.world_pos(2, 3, 4);
assert!((wp[0] - 2.0).abs() < 1e-5, "x mismatch {}", wp[0]);
assert!((wp[1] - 3.5).abs() < 1e-5, "y mismatch {}", wp[1]);
assert!((wp[2] - 5.0).abs() < 1e-5, "z mismatch {}", wp[2]);
}
#[test]
fn test_voxel_grid_density() {
let mut grid = VoxelGrid::new([4, 4, 4], [0.0; 3], 1.0);
assert!((grid.density() - 0.0).abs() < 1e-6);
for ix in 0..4 {
for iy in 0..4 {
grid.set(ix, iy, 0, true);
}
}
assert!(
(grid.density() - 0.25).abs() < 1e-5,
"density={}",
grid.density()
);
}
#[test]
fn test_mesh_bounds_triangle() {
let mesh = make_triangle_mesh([0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 4.0, 5.0]);
let (mn, mx) = mesh_bounds(&mesh);
assert!((mn[0] - 0.0).abs() < 1e-6);
assert!((mx[0] - 3.0).abs() < 1e-6);
assert!((mx[1] - 4.0).abs() < 1e-6);
assert!((mx[2] - 5.0).abs() < 1e-6);
}
#[test]
fn test_voxelize_surface_triangle() {
let mesh = make_triangle_mesh([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]);
let params = VoxelizeParams {
resolution: 8,
surface_only: true,
padding: 0.1,
};
let grid = voxelize_surface(&mesh, ¶ms);
assert!(
grid.voxel_count() > 0,
"expected at least one surface voxel"
);
assert!(grid.total_cells() > 0);
}
#[test]
fn test_voxelize_solid_simple() {
let mesh = make_quad_mesh();
let params = VoxelizeParams {
resolution: 8,
surface_only: false,
padding: 0.1,
};
let grid = voxelize_solid(&mesh, ¶ms);
assert!(grid.total_cells() > 0);
}
#[test]
fn test_voxel_to_mesh_single() {
let mut grid = VoxelGrid::new([1, 1, 1], [0.0; 3], 1.0);
grid.set(0, 0, 0, true);
let mesh = voxel_to_mesh(&grid);
assert_eq!(mesh.positions.len(), 24, "verts");
assert_eq!(mesh.indices.len(), 36, "indices");
}
#[test]
fn test_voxelize_params_default() {
let p = VoxelizeParams::default();
assert_eq!(p.resolution, 32);
assert!(!p.surface_only);
assert!((p.padding - 0.05).abs() < 1e-6);
}
#[test]
fn test_voxelize_resolution() {
let mesh = make_triangle_mesh([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let p8 = VoxelizeParams {
resolution: 8,
surface_only: true,
padding: 0.05,
};
let p16 = VoxelizeParams {
resolution: 16,
surface_only: true,
padding: 0.05,
};
let g8 = voxelize_surface(&mesh, &p8);
let g16 = voxelize_surface(&mesh, &p16);
assert!(g16.total_cells() >= g8.total_cells());
}
#[test]
fn test_voxelize_dispatch_surface_only() {
let mesh = make_triangle_mesh([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]);
let params = VoxelizeParams {
resolution: 8,
surface_only: true,
padding: 0.05,
};
let grid = voxelize(&mesh, ¶ms);
assert!(grid.voxel_count() > 0);
}
#[test]
fn test_voxelize_dispatch_solid() {
let mesh = make_triangle_mesh([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]);
let params = VoxelizeParams {
resolution: 8,
surface_only: false,
padding: 0.05,
};
let grid = voxelize(&mesh, ¶ms);
assert!(grid.total_cells() > 0);
}
}