use super::predicates::orient2d_any;
use super::retriangulate::projection_axis;
use super::{DropAxis, ImplicitPoint, Lpi, Sign};
type Tri = [[f64; 3]; 3];
#[inline]
fn e(p: [f64; 3]) -> ImplicitPoint {
ImplicitPoint::Explicit(p)
}
fn plane_normal(t: &Tri) -> [f64; 3] {
let u = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]];
let v = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]];
[
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
]
}
fn point_in_tri(q: [f64; 3], tri: &Tri, axis: DropAxis, w0: Sign) -> bool {
let opp = w0.flip();
let on_inner = |a: [f64; 3], b: [f64; 3]| orient2d_any(&e(a), &e(b), &e(q), axis) != opp;
on_inner(tri[0], tri[1]) && on_inner(tri[1], tri[2]) && on_inner(tri[2], tri[0])
}
fn seg_seg_cross(q0: [f64; 3], q1: [f64; 3], e0: [f64; 3], e1: [f64; 3], axis: DropAxis) -> bool {
let s1 = orient2d_any(&e(q0), &e(q1), &e(e0), axis);
let s2 = orient2d_any(&e(q0), &e(q1), &e(e1), axis);
let s3 = orient2d_any(&e(e0), &e(e1), &e(q0), axis);
let s4 = orient2d_any(&e(e0), &e(e1), &e(q1), axis);
s1 != Sign::Zero && s2 != Sign::Zero && s1 != s2 && s3 != Sign::Zero && s4 != Sign::Zero && s3 != s4
}
fn crossing_lpi(q0: [f64; 3], q1: [f64; 3], e0: [f64; 3], e1: [f64; 3], n: [f64; 3]) -> ImplicitPoint {
let m = n[0].abs().max(n[1].abs()).max(n[2].abs());
let ng = if m > 0.0 && m.is_finite() {
let s = 1024.0 / m; [(n[0] * s).round(), (n[1] * s).round(), (n[2] * s).round()]
} else {
n
};
let aux = [e0[0] + ng[0], e0[1] + ng[1], e0[2] + ng[2]];
ImplicitPoint::Lpi(Lpi { p: q0, q: q1, r: e0, s: e1, t: aux })
}
fn clip_seg(
q0: [f64; 3],
q1: [f64; 3],
ta: &Tri,
axis: DropAxis,
w0: Sign,
n: [f64; 3],
) -> Option<(ImplicitPoint, ImplicitPoint)> {
let in0 = point_in_tri(q0, ta, axis, w0);
let in1 = point_in_tri(q1, ta, axis, w0);
let edges = [(ta[0], ta[1]), (ta[1], ta[2]), (ta[2], ta[0])];
let crossings: Vec<ImplicitPoint> = edges
.iter()
.filter(|&&(e0, e1)| seg_seg_cross(q0, q1, e0, e1, axis))
.map(|&(e0, e1)| crossing_lpi(q0, q1, e0, e1, n))
.collect();
match (in0, in1) {
(true, true) => Some((e(q0), e(q1))),
(true, false) => crossings.into_iter().next().map(|c| (e(q0), c)),
(false, true) => crossings.into_iter().next().map(|c| (c, e(q1))),
(false, false) => {
if crossings.len() >= 2 {
Some((crossings[0].clone(), crossings[1].clone()))
} else {
None
}
}
}
}
pub fn coplanar_clip(ta: &Tri, tb: &Tri) -> Vec<(ImplicitPoint, ImplicitPoint)> {
let (axis, w0) = match projection_axis(ta) {
Some(x) => x,
None => return Vec::new(),
};
let n = plane_normal(ta);
let tb_edges = [(tb[0], tb[1]), (tb[1], tb[2]), (tb[2], tb[0])];
tb_edges
.iter()
.filter_map(|&(q0, q1)| clip_seg(q0, q1, ta, axis, w0, n))
.collect()
}
#[cfg(test)]
mod tests {
use super::super::predicates::orient3d;
use super::*;
#[test]
fn coplanar_clip_of_contained_face_is_its_edges_on_plane() {
let ta: Tri = [[0., 0., 0.], [6., 0., 0.], [0., 6., 0.]];
let tb: Tri = [[1., 1., 0.], [3., 1., 0.], [1., 3., 0.]];
let cons = coplanar_clip(&ta, &tb);
assert_eq!(cons.len(), 3, "contained face should give all 3 edges");
for (a, b) in &cons {
assert!(matches!(a, ImplicitPoint::Explicit(_)));
assert!(matches!(b, ImplicitPoint::Explicit(_)));
assert_eq!(orient3d(a, &e(ta[0]), &e(ta[1]), &e(ta[2])), Sign::Zero);
}
}
#[test]
fn coplanar_clip_of_overhanging_face_clips_to_aux_lpi_on_plane() {
let ta: Tri = [[0., 0., 0.], [4., 0., 0.], [0., 4., 0.]]; let tb: Tri = [[1., 1., 0.], [5., 1., 0.], [1., 5., 0.]]; let cons = coplanar_clip(&ta, &tb);
assert!(!cons.is_empty(), "overlapping faces must produce constraints");
let mut saw_lpi = false;
for (a, b) in &cons {
for ep in [a, b] {
if matches!(ep, ImplicitPoint::Lpi(_)) {
saw_lpi = true;
}
assert_eq!(
orient3d(ep, &e(ta[0]), &e(ta[1]), &e(ta[2])),
Sign::Zero,
"coplanar-clip endpoint off the shared plane"
);
}
}
assert!(saw_lpi, "an overhanging edge must yield an auxiliary-plane LPI crossing");
}
}