use crate::{Mesh, Point2, Point3, Vector3};
pub(crate) fn cap_half_space_clip(mesh: &mut Mesh, plane_point: Point3<f64>, clip_normal: Vector3<f64>) {
use crate::triangulation::{project_to_2d, triangulate_polygon_with_holes_refined};
use std::collections::{HashMap, HashSet};
if std::env::var_os("IFC_LITE_HALFSPACE_CAP_OFF").is_some() {
return;
}
let tri_n = mesh.indices.len() / 3;
let vcount = mesh.positions.len() / 3;
if tri_n == 0 || vcount < 3 {
return;
}
let n = match clip_normal.try_normalize(1e-12) {
Some(v) => v,
None => return,
};
let (mn, mx) = mesh.bounds();
let diag =
((mx.x - mn.x).powi(2) + (mx.y - mn.y).powi(2) + (mx.z - mn.z).powi(2)).sqrt() as f64;
let on_plane_eps = (diag * 1.0e-5).max(1.0e-6);
let weld_eps = on_plane_eps;
let quant = |c: f32| -> i64 { (c as f64 / weld_eps).round() as i64 };
let mut weld: HashMap<(i64, i64, i64), u32> = HashMap::new();
let mut pos: Vec<Point3<f64>> = Vec::new();
let mut on_plane: Vec<bool> = Vec::new();
let mut welded: Vec<u32> = Vec::with_capacity(vcount);
for i in 0..vcount {
let key = (
quant(mesh.positions[i * 3]),
quant(mesh.positions[i * 3 + 1]),
quant(mesh.positions[i * 3 + 2]),
);
let id = match weld.get(&key) {
Some(&id) => id,
None => {
let p = Point3::new(
mesh.positions[i * 3] as f64,
mesh.positions[i * 3 + 1] as f64,
mesh.positions[i * 3 + 2] as f64,
);
let id = pos.len() as u32;
on_plane.push((p - plane_point).dot(&n).abs() <= on_plane_eps);
pos.push(p);
weld.insert(key, id);
id
}
};
welded.push(id);
}
let mut present: HashSet<(u32, u32)> = HashSet::new();
for t in 0..tri_n {
let v = [
welded[mesh.indices[t * 3] as usize],
welded[mesh.indices[t * 3 + 1] as usize],
welded[mesh.indices[t * 3 + 2] as usize],
];
for (a, b) in [(v[0], v[1]), (v[1], v[2]), (v[2], v[0])] {
if a != b {
present.insert((a, b));
}
}
}
let mut next: HashMap<u32, u32> = HashMap::new();
for &(a, b) in &present {
if present.contains(&(b, a)) {
continue; }
if !on_plane[a as usize] || !on_plane[b as usize] {
continue; }
if next.insert(a, b).is_some() {
return; }
}
if next.is_empty() {
return;
}
let mut starts: Vec<u32> = next.keys().copied().collect();
starts.sort_unstable();
let mut visited: HashSet<u32> = HashSet::new();
let mut loops: Vec<Vec<u32>> = Vec::new();
for &s in &starts {
if visited.contains(&s) {
continue;
}
let mut loop_v: Vec<u32> = Vec::new();
let mut cur = s;
loop {
if !visited.insert(cur) {
break;
}
loop_v.push(cur);
match next.get(&cur) {
Some(&nx) => cur = nx,
None => {
loop_v.clear();
break;
}
}
if cur == s {
break;
}
}
if loop_v.len() >= 3 {
loops.push(loop_v);
}
}
if loops.is_empty() {
return;
}
let cap_outward = -n;
let mut all3d: Vec<Point3<f64>> = Vec::new();
let mut ranges: Vec<(usize, usize)> = Vec::new();
for lp in &loops {
let start = all3d.len();
for &i in lp {
all3d.push(pos[i as usize]);
}
ranges.push((start, all3d.len()));
}
let (all2d, u_axis, v_axis, origin) = project_to_2d(&all3d, &cap_outward);
let signed_area = |ring: &[Point2<f64>]| -> f64 {
let mut s = 0.0;
let m = ring.len();
for i in 0..m {
let j = (i + 1) % m;
s += ring[i].x * ring[j].y - ring[j].x * ring[i].y;
}
s * 0.5
};
let point_in_ring = |pt: &Point2<f64>, ring: &[Point2<f64>]| -> bool {
let mut inside = false;
let m = ring.len();
let mut j = m - 1;
for i in 0..m {
let (pi, pj) = (ring[i], ring[j]);
if ((pi.y > pt.y) != (pj.y > pt.y))
&& (pt.x < (pj.x - pi.x) * (pt.y - pi.y) / (pj.y - pi.y) + pi.x)
{
inside = !inside;
}
j = i;
}
inside
};
let rings: Vec<Vec<Point2<f64>>> = ranges
.iter()
.map(|&(a, b)| all2d[a..b].to_vec())
.collect();
let depth: Vec<usize> = (0..rings.len())
.map(|i| {
let probe = rings[i][0];
(0..rings.len())
.filter(|&j| j != i && point_in_ring(&probe, &rings[j]))
.count()
})
.collect();
let cap_normal = [cap_outward.x as f32, cap_outward.y as f32, cap_outward.z as f32];
let lift = |p: &Point2<f64>| -> Point3<f64> { origin + u_axis * p.x + v_axis * p.y };
for (oi, ring) in rings.iter().enumerate() {
if !depth[oi].is_multiple_of(2) {
continue; }
let mut outer = ring.clone();
if signed_area(&outer) < 0.0 {
outer.reverse();
}
let mut holes: Vec<Vec<Point2<f64>>> = Vec::new();
for (hi, hring) in rings.iter().enumerate() {
if hi == oi || depth[hi] != depth[oi] + 1 {
continue;
}
if !point_in_ring(&hring[0], ring) {
continue;
}
let mut h = hring.clone();
if signed_area(&h) > 0.0 {
h.reverse();
}
holes.push(h);
}
let (verts2d, indices) = match triangulate_polygon_with_holes_refined(&outer, &holes) {
Ok(r) => r,
Err(_) => continue,
};
let verts3d: Vec<Point3<f64>> = verts2d.iter().map(lift).collect();
let base = (mesh.positions.len() / 3) as u32;
for p in &verts3d {
mesh.positions
.extend_from_slice(&[p.x as f32, p.y as f32, p.z as f32]);
mesh.normals.extend_from_slice(&cap_normal);
}
for tri in indices.chunks_exact(3) {
let (a, b, c) = (verts3d[tri[0]], verts3d[tri[1]], verts3d[tri[2]]);
let geo_n = (b - a).cross(&(c - a));
let (i1, i2) = if geo_n.dot(&cap_outward) >= 0.0 {
(tri[1], tri[2])
} else {
(tri[2], tri[1])
};
mesh.indices
.extend_from_slice(&[base + tri[0] as u32, base + i1 as u32, base + i2 as u32]);
}
}
}