use axiolid_core::Point3;
use axiolid_mesh::TriMesh;
use std::collections::BTreeMap;
use crate::box_detect::AlignedBox;
struct Axis {
coords: Vec<f64>,
}
impl Axis {
fn build(lo: f64, hi: f64, cuts: impl Iterator<Item = f64>, eps: f64) -> Self {
let mut coords = vec![lo, hi];
for c in cuts {
if c > lo + eps && c < hi - eps {
coords.push(c);
}
}
coords.sort_by(|a, b| a.partial_cmp(b).expect("finite coordinates"));
coords.dedup_by(|a, b| (*a - *b).abs() <= eps);
Self { coords }
}
fn cells(&self) -> usize {
self.coords.len() - 1
}
fn mid(&self, i: usize) -> f64 {
0.5 * (self.coords[i] + self.coords[i + 1])
}
}
pub fn subtract_boxes(
host: &AlignedBox,
cutters: &[AlignedBox],
max_cells: usize,
) -> Option<TriMesh> {
if cutters.is_empty() {
return None;
}
let (lo, hi) = (host.min, host.max);
let span = (0..3).map(|a| hi[a] - lo[a]).fold(0.0, f64::max);
let eps = span * 1e-12;
let overlapping: Vec<&AlignedBox> = cutters
.iter()
.filter(|c| (0..3).all(|a| c.max[a] > lo[a] + eps && c.min[a] < hi[a] - eps))
.collect();
if overlapping.is_empty() {
return None;
}
let axes: Vec<Axis> = (0..3)
.map(|a| {
Axis::build(
lo[a],
hi[a],
overlapping.iter().flat_map(|c| [c.min[a], c.max[a]]),
eps,
)
})
.collect();
let (nx, ny, nz) = (axes[0].cells(), axes[1].cells(), axes[2].cells());
if nx.saturating_mul(ny).saturating_mul(nz) > max_cells {
return None;
}
let mut solid = vec![false; nx * ny * nz];
let at = |i: usize, j: usize, k: usize| (i * ny + j) * nz + k;
for i in 0..nx {
let cx = axes[0].mid(i);
for j in 0..ny {
let cy = axes[1].mid(j);
for k in 0..nz {
let c = [cx, cy, axes[2].mid(k)];
let inside_cutter = overlapping
.iter()
.any(|b| (0..3).all(|a| c[a] > b.min[a] && c[a] < b.max[a]));
solid[at(i, j, k)] = !inside_cutter;
}
}
}
let mut ids: BTreeMap<(usize, usize, usize), u32> = BTreeMap::new();
let mut positions: Vec<Point3> = Vec::new();
let mut vertex = |g: (usize, usize, usize), positions: &mut Vec<Point3>| -> u32 {
if let Some(v) = ids.get(&g) {
return *v;
}
let v = positions.len() as u32;
positions.push(Point3::new(
axes[0].coords[g.0],
axes[1].coords[g.1],
axes[2].coords[g.2],
));
ids.insert(g, v);
v
};
let mut indices: Vec<u32> = Vec::new();
const CYCLE: [(usize, usize); 3] = [(1, 2), (2, 0), (0, 1)];
for i in 0..nx {
for j in 0..ny {
for k in 0..nz {
if !solid[at(i, j, k)] {
continue;
}
let cell = [i, j, k];
let dims = [nx, ny, nz];
for a in 0..3 {
let (b, c) = CYCLE[a];
for &positive in &[true, false] {
let neighbour_solid = if positive {
cell[a] + 1 < dims[a] && {
let mut n = cell;
n[a] += 1;
solid[at(n[0], n[1], n[2])]
}
} else {
cell[a] > 0 && {
let mut n = cell;
n[a] -= 1;
solid[at(n[0], n[1], n[2])]
}
};
if neighbour_solid {
continue;
}
let plane = if positive { cell[a] + 1 } else { cell[a] };
let mut g = [0usize; 3];
g[a] = plane;
let corner = |db: usize, dc: usize, g: &[usize; 3]| {
let mut q = *g;
q[b] = cell[b] + db;
q[c] = cell[c] + dc;
(q[0], q[1], q[2])
};
let p00 = vertex(corner(0, 0, &g), &mut positions);
let p10 = vertex(corner(1, 0, &g), &mut positions);
let p11 = vertex(corner(1, 1, &g), &mut positions);
let p01 = vertex(corner(0, 1, &g), &mut positions);
if positive {
indices.extend_from_slice(&[p00, p10, p11, p00, p11, p01]);
} else {
indices.extend_from_slice(&[p00, p11, p10, p00, p01, p11]);
}
}
}
}
}
}
Some(TriMesh::new(positions, indices))
}