use super::super::interner::Vid;
use super::super::predicates::{orient2d_any, orient3d};
use super::super::rational::point_of;
use super::super::{DropAxis, ImplicitPoint, Sign};
use super::{Arrangement, BoolOp, Tri};
use num_traits::ToPrimitive;
pub(super) fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
}
pub(super) fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn e(p: [f64; 3]) -> ImplicitPoint {
ImplicitPoint::Explicit(p)
}
fn exact_seg_hits_tri(q1: [f64; 3], q2: [f64; 3], t: &Tri) -> bool {
let s1 = orient3d(&e(t[0]), &e(t[1]), &e(t[2]), &e(q1));
let s2 = orient3d(&e(t[0]), &e(t[1]), &e(t[2]), &e(q2));
if s1 == Sign::Zero || s2 == Sign::Zero || s1 == s2 {
return false;
}
let ea = orient3d(&e(q1), &e(q2), &e(t[0]), &e(t[1]));
let eb = orient3d(&e(q1), &e(q2), &e(t[1]), &e(t[2]));
let ec = orient3d(&e(q1), &e(q2), &e(t[2]), &e(t[0]));
ea != Sign::Zero && ea == eb && eb == ec
}
pub(super) fn operand_extent(tris: &[Tri]) -> f64 {
let mut hi = 1.0f64;
for t in tris {
for v in t {
for &c in v {
hi = hi.max(c.abs());
}
}
}
2.0 * hi + 1.0
}
fn ray_dir() -> [f64; 3] {
[0.301_511_3, 0.557_328_1, 0.773_890_1]
}
pub(super) fn point_inside(p: [f64; 3], tris: &[Tri], far_l: f64) -> bool {
let dir = ray_dir();
let far = [p[0] + dir[0] * far_l, p[1] + dir[1] * far_l, p[2] + dir[2] * far_l];
tris.iter().filter(|t| exact_seg_hits_tri(p, far, t)).count() % 2 == 1
}
fn point_inside_bvh(
p: [f64; 3],
tris: &[Tri],
bvh: &super::super::broadphase::Bvh,
far_l: f64,
scratch: &mut Vec<u32>,
) -> bool {
let dir = ray_dir();
let far = [p[0] + dir[0] * far_l, p[1] + dir[1] * far_l, p[2] + dir[2] * far_l];
scratch.clear();
bvh.ray_candidates(p, far, scratch);
scratch
.iter()
.filter(|&&i| exact_seg_hits_tri(p, far, &tris[i as usize]))
.count()
% 2
== 1
}
pub(super) fn to_f64_pt(arr: &Arrangement, v: Vid) -> [f64; 3] {
let idx = v as usize;
if let Some(Some(p)) = arr.f64_cache.borrow().get(idx).copied() {
return p;
}
let pt = arr.interner.get(v);
let p = arr
.interner
.lam(v)
.as_ref()
.and_then(super::super::fixed::point_to_f64_from_lam)
.or_else(|| super::super::fixed::point_to_f64(pt))
.unwrap_or_else(|| {
let q = point_of(pt);
[q[0].to_f64().unwrap(), q[1].to_f64().unwrap(), q[2].to_f64().unwrap()]
});
if let Some(slot) = arr.f64_cache.borrow_mut().get_mut(idx) {
*slot = Some(p);
}
p
}
pub(super) fn sub_f64(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn centroid(arr: &Arrangement, tri: [Vid; 3]) -> [f64; 3] {
let c = [to_f64_pt(arr, tri[0]), to_f64_pt(arr, tri[1]), to_f64_pt(arr, tri[2])];
[
(c[0][0] + c[1][0] + c[2][0]) / 3.0,
(c[0][1] + c[1][1] + c[2][1]) / 3.0,
(c[0][2] + c[1][2] + c[2][2]) / 3.0,
]
}
fn tri_normal(arr: &Arrangement, tri: [Vid; 3]) -> [f64; 3] {
let (a, b, c) = (to_f64_pt(arr, tri[0]), to_f64_pt(arr, tri[1]), to_f64_pt(arr, tri[2]));
cross3(sub_f64(b, a), sub_f64(c, a))
}
fn drop_axis_of(n: [f64; 3]) -> DropAxis {
let an = [n[0].abs(), n[1].abs(), n[2].abs()];
if an[0] >= an[1] && an[0] >= an[2] {
DropAxis::X
} else if an[1] >= an[2] {
DropAxis::Y
} else {
DropAxis::Z
}
}
fn point_in_tri_proj(c: [f64; 3], t: &Tri, n: [f64; 3]) -> bool {
let axis = drop_axis_of(n);
let w0 = orient2d_any(&e(t[0]), &e(t[1]), &e(t[2]), axis);
if w0 == Sign::Zero {
return false; }
let inside = |u: [f64; 3], v: [f64; 3]| orient2d_any(&e(u), &e(v), &e(c), axis) != w0.flip();
inside(t[0], t[1]) && inside(t[1], t[2]) && inside(t[2], t[0])
}
fn on_surface_tri(c: [f64; 3], t: &Tri) -> Option<[f64; 3]> {
if orient3d(&e(t[0]), &e(t[1]), &e(t[2]), &e(c)) != Sign::Zero {
return None; }
let n = cross3(sub_f64(t[1], t[0]), sub_f64(t[2], t[0]));
point_in_tri_proj(c, t, n).then_some(n)
}
fn near_on_surface_tri(c: [f64; 3], t: &Tri, band2: f64) -> Option<[f64; 3]> {
let n = cross3(sub_f64(t[1], t[0]), sub_f64(t[2], t[0]));
let nn = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
if nn <= 0.0 || !nn.is_finite() {
return None; }
let d = dot3(sub_f64(c, t[0]), n);
if (d * d) / nn > band2 {
return None; }
point_in_tri_proj(c, t, n).then_some(n)
}
fn on_surface_normal(c: [f64; 3], others: &[Tri]) -> Option<[f64; 3]> {
others.iter().find_map(|t| on_surface_tri(c, t))
}
use super::super::mesh_bridge::near_band_from_extent;
fn near_on_surface_normal(c: [f64; 3], others: &[Tri]) -> Option<[f64; 3]> {
let mut extent = 1.0f64;
for &x in &c {
extent = extent.max(x.abs());
}
for t in others {
for v in t {
for &x in v {
extent = extent.max(x.abs());
}
}
}
let band2 = near_band_from_extent(extent).powi(2);
others.iter().find_map(|t| near_on_surface_tri(c, t, band2))
}
fn c_on_or_near_a(
c: [f64; 3],
a: &[Tri],
bvh: &super::super::broadphase::Bvh,
a_coord_extent: f64,
scratch: &mut Vec<u32>,
) -> bool {
let extent = c.iter().fold(a_coord_extent, |m, &x| m.max(x.abs()));
let band = near_band_from_extent(extent);
let band2 = band * band;
scratch.clear();
bvh.point_candidates(c, band, scratch);
scratch.iter().any(|&i| {
let t = &a[i as usize];
on_surface_tri(c, t).is_some() || near_on_surface_tri(c, t, band2).is_some()
})
}
fn solid_side(c: [f64; 3], dir: [f64; 3], other: &[Tri], far_l: f64) -> (bool, bool) {
let len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
if len == 0.0 {
return (false, false);
}
let step = far_l * (1.0 / 1_048_576.0);
let u = [dir[0] / len * step, dir[1] / len * step, dir[2] / len * step];
let p_plus = [c[0] + u[0], c[1] + u[1], c[2] + u[2]];
let p_minus = [c[0] - u[0], c[1] - u[1], c[2] - u[2]];
(point_inside(p_plus, other, far_l), point_inside(p_minus, other, far_l))
}
pub(super) struct BComponents<'a> {
comps: &'a [&'a [Tri]],
aabbs: Vec<([f64; 3], [f64; 3])>,
exts: Vec<f64>,
}
impl<'a> BComponents<'a> {
pub(super) fn new(comps: &'a [&'a [Tri]]) -> Self {
let exts: Vec<f64> = comps.iter().map(|c| operand_extent(c)).collect();
let max_ext = exts.iter().cloned().fold(1.0f64, f64::max);
let pad = 4.0 * near_band_from_extent(max_ext);
let aabbs = comps
.iter()
.map(|c| {
let mut lo = [f64::MAX; 3];
let mut hi = [f64::MIN; 3];
for t in c.iter() {
for v in t {
for k in 0..3 {
lo[k] = lo[k].min(v[k]);
hi[k] = hi[k].max(v[k]);
}
}
}
for k in 0..3 {
lo[k] -= pad;
hi[k] += pad;
}
(lo, hi)
})
.collect();
Self { comps, aabbs, exts }
}
fn ray_may_hit(&self, k: usize, p: [f64; 3]) -> bool {
let dir = ray_dir();
let far_l = self.exts[k];
let q = [p[0] + dir[0] * far_l, p[1] + dir[1] * far_l, p[2] + dir[2] * far_l];
let (lo, hi) = (&self.aabbs[k].0, &self.aabbs[k].1);
let (mut t0, mut t1) = (0.0f64, 1.0f64);
for i in 0..3 {
let d = q[i] - p[i];
if d == 0.0 {
if p[i] < lo[i] || p[i] > hi[i] {
return false;
}
continue;
}
let (a, b) = ((lo[i] - p[i]) / d, (hi[i] - p[i]) / d);
let (a, b) = if a <= b { (a, b) } else { (b, a) };
t0 = t0.max(a);
t1 = t1.min(b);
if t0 > t1 {
return false;
}
}
true
}
#[inline]
fn point_in_aabb(&self, k: usize, p: [f64; 3]) -> bool {
let (lo, hi) = (&self.aabbs[k].0, &self.aabbs[k].1);
(0..3).all(|i| p[i] >= lo[i] && p[i] <= hi[i])
}
fn inside(&self, p: [f64; 3]) -> bool {
self.comps
.iter()
.enumerate()
.any(|(k, comp)| self.ray_may_hit(k, p) && point_inside(p, comp, self.exts[k]))
}
fn surface_normal(&self, c: [f64; 3]) -> Option<[f64; 3]> {
for (k, comp) in self.comps.iter().enumerate() {
if self.point_in_aabb(k, c) {
if let Some(n) = on_surface_normal(c, comp) {
return Some(n);
}
}
}
for (k, comp) in self.comps.iter().enumerate() {
if self.point_in_aabb(k, c) {
if let Some(n) = near_on_surface_normal(c, comp) {
return Some(n);
}
}
}
None
}
fn solid_side(&self, c: [f64; 3], dir: [f64; 3]) -> (bool, bool) {
let len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
if len == 0.0 {
return (false, false);
}
let max_ext = self.exts.iter().cloned().fold(1.0f64, f64::max);
let step = max_ext * (1.0 / 1_048_576.0);
let u = [dir[0] / len * step, dir[1] / len * step, dir[2] / len * step];
let p_plus = [c[0] + u[0], c[1] + u[1], c[2] + u[2]];
let p_minus = [c[0] - u[0], c[1] - u[1], c[2] - u[2]];
(self.inside(p_plus), self.inside(p_minus))
}
}
fn on_interface_keep(
arr: &Arrangement,
tri: [Vid; 3],
c: [f64; 3],
other: &[Tri],
ext_other: f64,
op: BoolOp,
) -> Option<bool> {
let n = tri_normal(arr, tri);
let (op_plus, op_minus) = solid_side(c, n, other, ext_other);
if op_plus == op_minus {
return None;
}
Some(match op {
BoolOp::Union => !op_plus,
BoolOp::Intersection => op_minus,
BoolOp::Difference => !op_minus,
})
}
pub(super) fn boolean_vids(arr: &Arrangement, a: &[Tri], b: &[Tri], op: BoolOp) -> Vec<[Vid; 3]> {
boolean_vids_components(arr, a, &BComponents::new(&[b]), op)
}
pub(super) fn boolean_vids_components(
arr: &Arrangement,
a: &[Tri],
bc: &BComponents,
op: BoolOp,
) -> Vec<[Vid; 3]> {
use std::collections::HashSet;
let ext_a = operand_extent(a);
let bvh_a = super::super::broadphase::Bvh::build(a);
let a_coord_extent = a
.iter()
.flat_map(|t| t.iter())
.flat_map(|v| v.iter())
.fold(1.0f64, |m, &x| m.max(x.abs()));
let mut scratch: Vec<u32> = Vec::new();
let dedup = matches!(op, BoolOp::Union | BoolOp::Intersection);
let mut a_kept: HashSet<[Vid; 3]> = HashSet::new();
let mut out = Vec::new();
for (i, &tri) in arr.tris_a.iter().enumerate() {
let c = centroid(arr, tri);
let cop_parent = arr.coplanar_a.get(i).copied().unwrap_or(false);
let keep;
if let Some(n_other) = bc.surface_normal(c) {
let co_oriented = dot3(tri_normal(arr, tri), n_other) > 0.0;
keep = match op {
BoolOp::Union | BoolOp::Intersection => co_oriented,
BoolOp::Difference => !co_oriented,
};
} else if let Some(k) = cop_parent
.then(|| {
let n = tri_normal(arr, tri);
let (op_plus, op_minus) = bc.solid_side(c, n);
if op_plus == op_minus {
return None; }
Some(match op {
BoolOp::Union => !op_plus,
BoolOp::Intersection => op_minus,
BoolOp::Difference => !op_minus,
})
})
.flatten()
{
keep = k;
} else {
let inside_b = bc.inside(c);
keep = match op {
BoolOp::Intersection => inside_b,
_ => !inside_b,
};
}
if keep {
if dedup {
a_kept.insert(rotate_min_first(tri));
}
out.push(tri);
}
}
for (i, &tri) in arr.tris_b.iter().enumerate() {
if dedup && a_kept.contains(&rotate_min_first(tri)) {
continue;
}
let c = centroid(arr, tri);
let cop_parent = arr.coplanar_b.get(i).copied().unwrap_or(false);
if c_on_or_near_a(c, a, &bvh_a, a_coord_extent, &mut scratch) {
continue; }
if cop_parent {
if let Some(keep) = on_interface_keep(arr, tri, c, a, ext_a, op) {
if keep {
let flip = matches!(op, BoolOp::Difference);
out.push(if flip { [tri[0], tri[2], tri[1]] } else { tri });
}
continue;
}
}
let inside_a = point_inside_bvh(c, a, &bvh_a, ext_a, &mut scratch);
let (keep, flip) = match op {
BoolOp::Difference => (inside_a, true),
BoolOp::Union => (!inside_a, false),
BoolOp::Intersection => (inside_a, false),
};
if keep {
out.push(if flip { [tri[0], tri[2], tri[1]] } else { tri });
}
}
out
}
#[inline]
pub(super) fn rotate_min_first(t: [Vid; 3]) -> [Vid; 3] {
let i = (0..3).min_by_key(|&k| t[k]).unwrap();
[t[i], t[(i + 1) % 3], t[(i + 2) % 3]]
}