use crate::csg::ClippingProcessor;
use crate::{Mesh, Point3, Vector3};
use nalgebra::Matrix3;
use rustc_hash::FxHashMap;
pub(super) struct OpeningBox {
pub(super) center: Vector3<f64>,
pub(super) axes: [Vector3<f64>; 3],
pub(super) half: [f64; 3],
}
impl OpeningBox {
fn thin_axis(&self) -> usize {
(0..3)
.min_by(|&i, &j| self.half[i].partial_cmp(&self.half[j]).unwrap())
.unwrap()
}
pub(super) fn extended_box_mesh(&self, origin: [f64; 3], extend: f64) -> Mesh {
let thin = self.thin_axis();
let mut half = self.half;
half[thin] += extend;
let corner = |sx: f64, sy: f64, sz: f64| -> Point3<f64> {
let w = self.center
+ self.axes[0] * (sx * half[0])
+ self.axes[1] * (sy * half[1])
+ self.axes[2] * (sz * half[2]);
Point3::new(w.x - origin[0], w.y - origin[1], w.z - origin[2])
};
let c = [
corner(-1.0, -1.0, -1.0),
corner(1.0, -1.0, -1.0),
corner(1.0, 1.0, -1.0),
corner(-1.0, 1.0, -1.0),
corner(-1.0, -1.0, 1.0),
corner(1.0, -1.0, 1.0),
corner(1.0, 1.0, 1.0),
corner(-1.0, 1.0, 1.0),
];
make_obb_mesh(&c)
}
}
fn make_obb_mesh(corners: &[Point3<f64>; 8]) -> Mesh {
let faces: [[usize; 4]; 6] = [
[0, 3, 2, 1],
[4, 5, 6, 7],
[0, 1, 5, 4],
[2, 3, 7, 6],
[0, 4, 7, 3],
[1, 2, 6, 5],
];
let mut m = Mesh::with_capacity(24, 36);
for idx in &faces {
let a = corners[idx[0]];
let b = corners[idx[1]];
let cc = corners[idx[2]];
let nrm = (b - a)
.cross(&(cc - a))
.try_normalize(1.0e-12)
.unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0));
let base = m.vertex_count() as u32;
m.add_vertex(corners[idx[0]], nrm);
m.add_vertex(corners[idx[1]], nrm);
m.add_vertex(corners[idx[2]], nrm);
m.add_vertex(corners[idx[3]], nrm);
m.add_triangle(base, base + 1, base + 2);
m.add_triangle(base, base + 2, base + 3);
}
m
}
pub(super) fn cutter_is_closed_manifold(m: &Mesh) -> bool {
if m.indices.len() < 12 {
return false;
}
let w = m.welded_by_position(1.0e-5);
if w.indices.len() < 12 {
return false;
}
let mut edge_uses: FxHashMap<(u32, u32), u32> = FxHashMap::default();
for t in w.indices.chunks_exact(3) {
if t[0] == t[1] || t[1] == t[2] || t[0] == t[2] {
return false; }
for &(a, b) in &[(t[0], t[1]), (t[1], t[2]), (t[2], t[0])] {
let key = if a < b { (a, b) } else { (b, a) };
*edge_uses.entry(key).or_insert(0) += 1;
}
}
edge_uses.values().all(|&c| c == 2)
}
pub(super) fn opening_obb_if_malformed(m: &Mesh) -> Option<OpeningBox> {
if cutter_is_closed_manifold(m) {
return None;
}
let all: Vec<Vector3<f64>> = m
.positions
.chunks_exact(3)
.map(|p| {
Vector3::new(
p[0] as f64 + m.origin[0],
p[1] as f64 + m.origin[1],
p[2] as f64 + m.origin[2],
)
})
.collect();
if all.len() < 8 {
return None;
}
if all.iter().any(|v| v.iter().any(|c| !c.is_finite())) {
return None;
}
let median_axis = |axis: usize| -> f64 {
let mut vals: Vec<f64> = all.iter().map(|v| v[axis]).collect();
vals.sort_by(f64::total_cmp);
vals[vals.len() / 2]
};
let med = Vector3::new(median_axis(0), median_axis(1), median_axis(2));
let mut dist: Vec<(f64, usize)> = all
.iter()
.enumerate()
.map(|(i, v)| ((v - med).norm(), i))
.collect();
dist.sort_by(|a, b| a.0.total_cmp(&b.0));
const FAR_M: f64 = 4.0;
let near_radius = dist[dist.len() / 2].0; let mut split_at = dist.len();
let mut found = false;
for i in (dist.len() / 2)..(dist.len() - 1) {
let gap = dist[i + 1].0 - dist[i].0;
if dist[i + 1].0 > FAR_M && dist[i + 1].0 > 3.0 * near_radius.max(1.0e-3) && gap > dist[i].0
{
split_at = i + 1;
found = true;
break;
}
}
if !found {
return None;
}
let inliers: Vec<Vector3<f64>> = dist[..split_at].iter().map(|(_, i)| all[*i]).collect();
if inliers.len() < 8 {
return None;
}
let n = inliers.len() as f64;
let mut c = Vector3::zeros();
for v in &inliers {
c += v;
}
c /= n;
let mut cov = Matrix3::zeros();
for v in &inliers {
let d = v - c;
cov += d * d.transpose();
}
cov /= n;
let eig = cov.symmetric_eigen();
let a0 = eig
.eigenvectors
.column(0)
.into_owned()
.try_normalize(1.0e-9)?;
let a1 = eig
.eigenvectors
.column(1)
.into_owned()
.try_normalize(1.0e-9)?;
let a2 = a0.cross(&a1).try_normalize(1.0e-9)?;
let axes = [a0, a1, a2];
let mut lo = [f64::MAX; 3];
let mut hi = [f64::MIN; 3];
for v in &inliers {
for k in 0..3 {
let t = v.dot(&axes[k]);
lo[k] = lo[k].min(t);
hi[k] = hi[k].max(t);
}
}
let half = [
(hi[0] - lo[0]) * 0.5,
(hi[1] - lo[1]) * 0.5,
(hi[2] - lo[2]) * 0.5,
];
if half.iter().any(|&h| h < 1.0e-3) {
return None;
}
let mid = [
(hi[0] + lo[0]) * 0.5,
(hi[1] + lo[1]) * 0.5,
(hi[2] + lo[2]) * 0.5,
];
let center = axes[0] * mid[0] + axes[1] * mid[1] + axes[2] * mid[2];
Some(OpeningBox { center, axes, half })
}
pub(super) fn recut_malformed_openings(result: &mut Mesh, boxes: &[OpeningBox]) {
if boxes.is_empty() || result.indices.is_empty() {
return;
}
let clipper = ClippingProcessor::new();
for bx in boxes {
let box_mesh = bx.extended_box_mesh(result.origin, 2.0);
if let Ok(cut) = clipper.subtract_mesh(result, &box_mesh) {
if !cut.is_empty() {
let origin = result.origin;
*result = cut;
result.origin = origin;
}
}
}
}
pub(super) fn world_host_bounds(mesh: &Mesh) -> ((f32, f32, f32), (f32, f32, f32)) {
let o = mesh.origin;
let (mn, mx) = mesh.bounds();
(
(
(mn.x as f64 + o[0]) as f32,
(mn.y as f64 + o[1]) as f32,
(mn.z as f64 + o[2]) as f32,
),
(
(mx.x as f64 + o[0]) as f32,
(mx.y as f64 + o[1]) as f32,
(mx.z as f64 + o[2]) as f32,
),
)
}
pub(super) fn translate_cutter_mesh(mesh: &Mesh, host_origin: [f64; 3]) -> Mesh {
let o = mesh.origin;
let mut positions = Vec::with_capacity(mesh.positions.len());
for c in mesh.positions.chunks_exact(3) {
positions.push((c[0] as f64 + o[0] - host_origin[0]) as f32);
positions.push((c[1] as f64 + o[1] - host_origin[1]) as f32);
positions.push((c[2] as f64 + o[2] - host_origin[2]) as f32);
}
Mesh {
positions,
normals: mesh.normals.clone(),
indices: mesh.indices.clone(),
rtc_applied: mesh.rtc_applied,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
welded_in_object_frame: false,
}
}