use crate::bvh::Bvh;
use crate::voxel::grid::Grid;
use glam::Vec3;
use rayon::prelude::*;
use std::collections::HashMap;
pub(crate) struct Signed<'a> {
bvh: &'a Bvh,
corner: Vec<u32>,
vertex_n: Vec<Vec3>,
edge_n: Vec<[Vec3; 3]>,
face_n: Vec<Vec3>,
}
impl<'a> Signed<'a> {
pub(crate) fn new(bvh: &'a Bvh) -> Signed<'a> {
let tris = &bvh.triangles;
let key = |p: Vec3| p.to_array().map(f32::to_bits);
let mut ids: HashMap<[u32; 3], u32> = HashMap::with_capacity(tris.len());
let mut corner = Vec::with_capacity(tris.len() * 3);
for t in tris {
for p in t {
let n = ids.len() as u32;
corner.push(*ids.entry(key(*p)).or_insert(n));
}
}
let face_n: Vec<Vec3> = tris
.par_iter()
.map(|t| (t[1] - t[0]).cross(t[2] - t[0]).normalize_or_zero())
.collect();
let mut vertex_n = vec![Vec3::ZERO; ids.len()];
let mut edges: HashMap<(u32, u32), Vec3> = HashMap::with_capacity(tris.len() * 2);
for (f, t) in tris.iter().enumerate() {
let n = face_n[f];
for k in 0..3 {
let (a, b, c) = (t[k], t[(k + 1) % 3], t[(k + 2) % 3]);
let angle = (b - a).normalize_or_zero().dot((c - a).normalize_or_zero());
vertex_n[corner[3 * f + k] as usize] += n * angle.clamp(-1.0, 1.0).acos();
let (i, j) = (corner[3 * f + k], corner[3 * f + (k + 1) % 3]);
*edges.entry((i.min(j), i.max(j))).or_insert(Vec3::ZERO) += n;
}
}
let edge_n = (0..tris.len())
.map(|f| {
[0, 1, 2].map(|k| {
let (i, j) = (corner[3 * f + k], corner[3 * f + (k + 1) % 3]);
edges[&(i.min(j), i.max(j))]
})
})
.collect();
Signed {
bvh,
corner,
vertex_n,
edge_n,
face_n,
}
}
pub(crate) fn distance(&self, p: Vec3, max: f32) -> Option<f32> {
let h = self.bvh.closest_point(p, max)?;
let f = h.triangle as usize;
let w = h.barycentrics;
const EPS: f32 = 1e-5;
let zero = [w.x <= EPS, w.y <= EPS, w.z <= EPS];
let n = match zero.iter().filter(|&&z| z).count() {
0 => self.face_n[f],
1 => {
let k = zero.iter().position(|&z| z).unwrap();
self.edge_n[f][(k + 1) % 3]
}
_ => {
let k = zero.iter().position(|&z| !z).unwrap_or(0);
self.vertex_n[self.corner[3 * f + k] as usize]
}
};
let d = p - h.point;
Some(if d.dot(n) < 0.0 {
-h.distance
} else {
h.distance
})
}
}
const WINDING_DIRS: [Vec3; 3] = [
Vec3::new(0.577_350_3, 0.611_201_6, 0.541_400_1),
Vec3::new(-0.701_793_8, 0.120_391_6, -0.702_186_7),
Vec3::new(0.231_224_6, -0.940_996_3, 0.247_002_9),
];
pub(crate) fn relabel_boundary(
grid: &Grid,
solid: &mut [bool],
surface: &[bool],
signed: &Signed,
) -> usize {
let n = grid.len();
let cell = grid.cell;
let [nx, ny, nz] = grid.dims;
let inside: Vec<Option<(bool, bool)>> = (0..n)
.into_par_iter()
.with_min_len(1024)
.map(|i| {
if !(solid[i] && surface[i]) {
return None;
}
let [x, y, z] = grid.coords(i);
let c = grid.center(x, y, z);
let d = signed.distance(c, 1.5 * cell)?;
let w = WINDING_DIRS.map(|dir| signed.bvh.ray_winding(c, dir));
let enclosed_any = w.iter().any(|&k| k > 0);
let enclosed_all = w.iter().all(|&k| k > 0);
Some((d < 0.0 || enclosed_any, d < 0.0 && enclosed_all))
})
.collect();
let centre_in = |j: usize| solid[j] && inside[j].is_none_or(|(_, sure)| sure);
let tris = &signed.bvh.triangles;
let unsupported: Vec<u32> = tris
.par_iter()
.enumerate()
.flat_map_iter(|(f, t)| {
let nrm = signed.face_n[f];
let longest = (t[1] - t[0])
.length()
.max((t[2] - t[1]).length())
.max((t[0] - t[2]).length());
let k = ((longest / (0.5 * cell)).ceil() as usize).clamp(1, 256);
let t = *t;
let mut out = Vec::new();
if nrm == Vec3::ZERO {
return out.into_iter();
}
for a in 0..=k {
for b in 0..=k - a {
let (u, v) = (a as f32 / k as f32, b as f32 / k as f32);
let p = t[0] + (t[1] - t[0]) * u + (t[2] - t[0]) * v;
let Some([x, y, z]) = grid.cell_of(p) else {
continue;
};
let (mut ok, mut any_in) = (false, false);
'search: for dz in -1i32..=1 {
for dy in -1i32..=1 {
for dx in -1i32..=1 {
let (cx, cy, cz) = (x as i32 + dx, y as i32 + dy, z as i32 + dz);
if cx < 0
|| cy < 0
|| cz < 0
|| cx >= nx as i32
|| cy >= ny as i32
|| cz >= nz as i32
{
continue;
}
let (cx, cy, cz) = (cx as usize, cy as usize, cz as usize);
let j = grid.idx(cx, cy, cz);
let off = grid.center(cx, cy, cz) - p;
if solid[j] && (!surface[j] || centre_in(j)) {
any_in = true;
}
if centre_in(j) && off.dot(nrm) < 0.0 && off.length() <= cell {
ok = true;
break 'search;
}
}
}
}
if !ok {
let sheet = if any_in { 0 } else { 1 << 31 };
out.push(grid.idx(x, y, z) as u32 | sheet);
}
}
}
out.into_iter()
})
.collect();
let mut keep = vec![false; n];
for &c in &unsupported {
let cell_i = (c & !(1 << 31)) as usize;
keep[cell_i] = true;
if c & (1 << 31) == 0 {
continue;
}
let [x, y, z] = grid.coords(cell_i);
for dz in -1i32..=1 {
for dy in -1i32..=1 {
for dx in -1i32..=1 {
let (a, b, cz) = (x as i32 + dx, y as i32 + dy, z as i32 + dz);
if a < 0
|| b < 0
|| cz < 0
|| a >= nx as i32
|| b >= ny as i32
|| cz >= nz as i32
{
continue;
}
let j = grid.idx(a as usize, b as usize, cz as usize);
if surface[j] {
keep[j] = true;
}
}
}
}
}
let carvable: Vec<bool> = (0..n)
.map(|i| inside[i].is_some_and(|(inn, _)| !inn) && !keep[i])
.collect();
let mut carved = 0;
for i in 0..n {
if carvable[i] {
solid[i] = false;
carved += 1;
}
}
carved
}