use super::coplanar::coplanar_clip;
use super::interner::{Interner, Vid};
use super::predicates::{cmp_lex, orient2d_any};
use super::retriangulate::{projection_axis, triangulate, Constraint, RetriInput};
use super::tritri::{tri_tri_intersection, TriTri};
use super::{ImplicitPoint, Sign, Tpi};
use std::cmp::Ordering;
mod boolean;
mod classify;
#[cfg(test)]
mod tests;
pub use self::boolean::{
boolean, boolean_manifest, boolean_topology_hash, box_mesh, cube_mesh, difference_all,
difference_all_lenient, union_all,
};
pub type Tri = [[f64; 3]; 3];
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BoolOp {
Difference,
Union,
Intersection,
}
pub struct Arrangement {
pub interner: Interner,
pub tris_a: Vec<[Vid; 3]>,
pub tris_b: Vec<[Vid; 3]>,
pub unrecovered: usize,
pub coplanar_a: Vec<bool>,
pub coplanar_b: Vec<bool>,
f64_cache: std::cell::RefCell<Vec<Option<[f64; 3]>>>,
}
struct RawSeg {
a: ImplicitPoint,
b: ImplicitPoint,
cutter: Tri,
}
#[inline]
fn tri_plane(t: &Tri) -> [[f64; 3]; 3] {
*t
}
type EndLam<'a> = (&'a super::interval::IvLam, &'a ImplicitPoint);
#[inline]
fn orient2d_end(a: EndLam, b: EndLam, c: EndLam, axis: super::DropAxis) -> Sign {
super::interval::orient2d_from_lam_iv(a.0, b.0, c.0, axis)
.unwrap_or_else(|| orient2d_any(a.1, b.1, c.1, axis))
}
fn segments_cross(a1: EndLam, b1: EndLam, a2: EndLam, b2: EndLam, axis: super::DropAxis) -> bool {
let s1 = orient2d_end(a1, b1, a2, axis);
let s2 = orient2d_end(a1, b1, b2, axis);
let s3 = orient2d_end(a2, b2, a1, axis);
let s4 = orient2d_end(a2, b2, b1, axis);
s1 != Sign::Zero && s2 != Sign::Zero && s1 != s2 && s3 != Sign::Zero && s4 != Sign::Zero && s3 != s4
}
fn split_crossings(t: &Tri, raws: &[RawSeg]) -> Vec<Constraint> {
let axis = match projection_axis(t) {
Some((a, _)) => a,
None => return Vec::new(),
};
let n = raws.len();
let iv: Vec<(super::interval::IvLam, super::interval::IvLam)> = raws
.iter()
.map(|r| (super::interval::ilambda_cached(&r.a), super::interval::ilambda_cached(&r.b)))
.collect();
let mut splits: Vec<Vec<ImplicitPoint>> = vec![Vec::new(); n];
for k in 0..n {
let (ka, kb) = ((&iv[k].0, &raws[k].a), (&iv[k].1, &raws[k].b));
for l in (k + 1)..n {
let (la, lb) = ((&iv[l].0, &raws[l].a), (&iv[l].1, &raws[l].b));
if segments_cross(ka, kb, la, lb, axis) {
let x = ImplicitPoint::Tpi(Tpi {
planes: [tri_plane(t), tri_plane(&raws[k].cutter), tri_plane(&raws[l].cutter)],
});
splits[k].push(x.clone());
splits[l].push(x);
}
}
}
let mut out = Vec::new();
for k in 0..n {
let mut chain = vec![raws[k].a.clone()];
chain.append(&mut splits[k]);
chain.push(raws[k].b.clone());
chain.sort_by(|p, q| match cmp_lex(p, q) {
Sign::Negative => Ordering::Less,
Sign::Positive => Ordering::Greater,
Sign::Zero => Ordering::Equal,
});
chain.dedup_by(|p, q| cmp_lex(p, q) == Sign::Zero);
for w in chain.windows(2) {
out.push(Constraint { a: w[0].clone(), b: w[1].clone() });
}
}
out
}
pub fn arrange(a: &[Tri], b: &[Tri]) -> Arrangement {
let mut raw_a: Vec<Vec<RawSeg>> = (0..a.len()).map(|_| Vec::new()).collect();
let mut raw_b: Vec<Vec<RawSeg>> = (0..b.len()).map(|_| Vec::new()).collect();
let mut cop_a: Vec<Vec<Constraint>> = (0..a.len()).map(|_| Vec::new()).collect();
let mut cop_b: Vec<Vec<Constraint>> = (0..b.len()).map(|_| Vec::new()).collect();
let mut pt_a: Vec<Vec<ImplicitPoint>> = (0..a.len()).map(|_| Vec::new()).collect();
let mut pt_b: Vec<Vec<ImplicitPoint>> = (0..b.len()).map(|_| Vec::new()).collect();
let pairs = super::broadphase::candidate_pairs(a, b);
for (i, j) in pairs {
if super::budget::tripped() {
break;
}
let (ta, tb) = (&a[i], &b[j]);
match tri_tri_intersection(ta, tb) {
TriTri::Segment([s, t]) => {
raw_a[i].push(RawSeg { a: s.clone(), b: t.clone(), cutter: *tb });
raw_b[j].push(RawSeg { a: s, b: t, cutter: *ta });
}
TriTri::Coplanar => {
cop_a[i].extend(coplanar_clip(ta, tb).into_iter().map(|(a, b)| Constraint { a, b }));
cop_b[j].extend(coplanar_clip(tb, ta).into_iter().map(|(a, b)| Constraint { a, b }));
}
TriTri::Point(p) => {
pt_a[i].push(p.clone());
pt_b[j].push(p);
}
TriTri::None => {}
}
}
let build = |tris: &[Tri], raw: &[Vec<RawSeg>], cop: &mut [Vec<Constraint>]| -> Vec<Vec<Constraint>> {
(0..tris.len())
.map(|i| {
let mut c = split_crossings(&tris[i], &raw[i]);
c.append(&mut cop[i]);
c
})
.collect()
};
let cop_parent_a: Vec<bool> = cop_a.iter().map(|c| !c.is_empty()).collect();
let cop_parent_b: Vec<bool> = cop_b.iter().map(|c| !c.is_empty()).collect();
let ca = build(a, &raw_a, &mut cop_a);
let cb = build(b, &raw_b, &mut cop_b);
let mut interner = Interner::new();
let mut unrecovered = 0usize;
let (tris_a, coplanar_a) =
retriangulate_each(a, &ca, &pt_a, &cop_parent_a, &mut interner, &mut unrecovered);
let (tris_b, coplanar_b) =
retriangulate_each(b, &cb, &pt_b, &cop_parent_b, &mut interner, &mut unrecovered);
let n_pts = interner.len();
Arrangement {
interner,
tris_a,
tris_b,
coplanar_a,
coplanar_b,
unrecovered,
f64_cache: std::cell::RefCell::new(vec![None; n_pts]),
}
}
pub struct MultiArrangement {
pub interner: Interner,
pub subtris: Vec<Vec<[Vid; 3]>>,
pub unrecovered: usize,
}
pub fn arrange_many(meshes: &[&[Tri]]) -> MultiArrangement {
let n = meshes.len();
let mut raw: Vec<Vec<Vec<RawSeg>>> =
meshes.iter().map(|m| (0..m.len()).map(|_| Vec::new()).collect()).collect();
let mut cop: Vec<Vec<Vec<Constraint>>> =
meshes.iter().map(|m| (0..m.len()).map(|_| Vec::new()).collect()).collect();
let mut pts: Vec<Vec<Vec<ImplicitPoint>>> =
meshes.iter().map(|m| (0..m.len()).map(|_| Vec::new()).collect()).collect();
for i in 0..n {
if super::budget::tripped() {
break;
}
for j in (i + 1)..n {
let pairs = super::broadphase::candidate_pairs(meshes[i], meshes[j]);
for (ti, tj) in pairs {
if super::budget::tripped() {
break;
}
let (ta, tb) = (&meshes[i][ti], &meshes[j][tj]);
match tri_tri_intersection(ta, tb) {
TriTri::Segment([s, t]) => {
raw[i][ti].push(RawSeg { a: s.clone(), b: t.clone(), cutter: *tb });
raw[j][tj].push(RawSeg { a: s, b: t, cutter: *ta });
}
TriTri::Coplanar => {
cop[i][ti].extend(
coplanar_clip(ta, tb).into_iter().map(|(a, b)| Constraint { a, b }),
);
cop[j][tj].extend(
coplanar_clip(tb, ta).into_iter().map(|(a, b)| Constraint { a, b }),
);
}
TriTri::Point(p) => {
pts[i][ti].push(p.clone());
pts[j][tj].push(p);
}
TriTri::None => {}
}
}
}
}
let mut interner = Interner::new();
let mut subtris = Vec::with_capacity(n);
let mut unrecovered = 0usize;
for k in 0..n {
let cop_parent: Vec<bool> = cop[k].iter().map(|c| !c.is_empty()).collect();
let cons: Vec<Vec<Constraint>> = (0..meshes[k].len())
.map(|t| {
let mut c = split_crossings(&meshes[k][t], &raw[k][t]);
c.append(&mut cop[k][t]);
c
})
.collect();
let (tris, _coplanar) =
retriangulate_each(meshes[k], &cons, &pts[k], &cop_parent, &mut interner, &mut unrecovered);
subtris.push(tris);
}
MultiArrangement { interner, subtris, unrecovered }
}
fn retriangulate_each(
tris: &[Tri],
cons: &[Vec<Constraint>],
pts: &[Vec<ImplicitPoint>],
cop_parent: &[bool],
it: &mut Interner,
unrecovered: &mut usize,
) -> (Vec<[Vid; 3]>, Vec<bool>) {
let mut out = Vec::new();
let mut coplanar = Vec::new();
for (i, t) in tris.iter().enumerate() {
if super::budget::tripped() {
break;
}
let parent_cop = cop_parent.get(i).copied().unwrap_or(false);
let passthrough = |it: &mut Interner| {
[
it.intern(ImplicitPoint::Explicit(t[0])),
it.intern(ImplicitPoint::Explicit(t[1])),
it.intern(ImplicitPoint::Explicit(t[2])),
]
};
let before = out.len();
let tri_pts = pts.get(i).cloned().unwrap_or_default();
if cons[i].is_empty() && tri_pts.is_empty() {
out.push(passthrough(it));
} else if let Some(mesh) = triangulate(
&RetriInput { tri: *t, constraints: cons[i].clone(), points: tri_pts },
it,
) {
*unrecovered += mesh.unrecovered;
out.extend(mesh.tris);
} else {
out.push(passthrough(it)); }
coplanar.resize(out.len(), parent_cop);
debug_assert!(coplanar.len() == out.len() && before <= out.len());
}
(out, coplanar)
}