use std::collections::BTreeMap;
use axiolid_contracts::{GeomError, GeomResult, Sign};
use axiolid_core::{Point2, Point3};
use crate::intersection::{IntersectionSegment, NodeKey};
use crate::orient2d;
#[derive(Debug, Clone)]
pub struct FacePatch {
pub points: Vec<Point3>,
pub sources: Vec<Option<NodeKey>>,
pub triangles: Vec<[u32; 3]>,
}
fn dominant_axis(normal: Point3) -> usize {
let absolute = normal.abs();
if absolute.x >= absolute.y && absolute.x >= absolute.z {
0
} else if absolute.y >= absolute.z {
1
} else {
2
}
}
fn project(point: Point3, axis: usize) -> Point2 {
match axis {
0 => Point2::new(point.y, point.z),
1 => Point2::new(point.x, point.z),
_ => Point2::new(point.x, point.y),
}
}
pub fn retriangulate_face(
corners: [Point3; 3],
segments: &[IntersectionSegment],
positions: &BTreeMap<NodeKey, Point3>,
) -> GeomResult<FacePatch> {
let normal = (corners[1] - corners[0]).cross(corners[2] - corners[0]);
if normal.length_squared() == 0.0 {
return Err(GeomError::Degenerate(
"cannot retriangulate a degenerate face".into(),
));
}
let mut points: Vec<Point3> = corners.to_vec();
let mut sources: Vec<Option<NodeKey>> = vec![None; 3];
let mut index_of: BTreeMap<NodeKey, u32> = BTreeMap::new();
let mut edge_nodes: Vec<(NodeKey, Point3)> = Vec::new();
for (&node, &point) in positions {
if corners.contains(&point) {
continue;
}
if point_on_face_edge(point, corners) {
edge_nodes.push((node, point));
}
}
for (node, point) in edge_nodes {
if index_of.contains_key(&node) {
continue;
}
index_of.insert(node, points.len() as u32);
points.push(point);
sources.push(Some(node));
}
if segments.is_empty() && points.len() == 3 {
return Ok(FacePatch {
points: corners.to_vec(),
sources: vec![None; 3],
triangles: vec![[0, 1, 2]],
});
}
for segment in segments {
for node in [segment.start, segment.end] {
if index_of.contains_key(&node) {
continue;
}
let point = *positions.get(&node).ok_or_else(|| {
GeomError::Degenerate("curve node has no recorded position".into())
})?;
if let Some(corner) = corners.iter().position(|&c| c == point) {
index_of.insert(node, corner as u32);
sources[corner] = Some(node);
continue;
}
index_of.insert(node, points.len() as u32);
points.push(point);
sources.push(Some(node));
}
}
let axis = dominant_axis(normal);
let flat: Vec<Point2> = points.iter().map(|&p| project(p, axis)).collect();
let mut constraints: Vec<(u32, u32)> = Vec::new();
for segment in segments {
let start = index_of[&segment.start];
let end = index_of[&segment.end];
if start != end {
constraints.push((start.min(end), start.max(end)));
}
}
constraints.sort_unstable();
constraints.dedup();
let triangles = triangulate_with_constraints(&flat, &constraints)?;
let triangles = triangles
.into_iter()
.map(|tri| {
let [a, b, c] = tri.map(|i| points[i as usize]);
if (b - a).cross(c - a).dot(normal) < 0.0 {
[tri[0], tri[2], tri[1]]
} else {
tri
}
})
.collect();
Ok(FacePatch {
points,
sources,
triangles,
})
}
fn triangulate_with_constraints(
points: &[Point2],
constraints: &[(u32, u32)],
) -> GeomResult<Vec<[u32; 3]>> {
let count = points.len();
let mut triangles: Vec<[u32; 3]> = Vec::new();
for a in 0..count {
for b in (a + 1)..count {
for c in (b + 1)..count {
let tri = [a as u32, b as u32, c as u32];
let [pa, pb, pc] = [points[a], points[b], points[c]];
if sign(orient2d(pa, pb, pc)) == Sign::Zero {
continue;
}
if (0..count).any(|other| {
other != a
&& other != b
&& other != c
&& point_inside(points[other], [pa, pb, pc])
}) {
continue;
}
if crosses_a_constraint(tri, points, constraints) {
continue;
}
triangles.push(tri);
}
}
}
triangles.sort_by(|left, right| {
let area = |t: &[u32; 3]| {
let [a, b, c] = t.map(|i| points[i as usize]);
((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs()
};
area(left)
.partial_cmp(&area(right))
.expect("finite coordinates give comparable areas")
.then_with(|| left.cmp(right))
});
let mut kept: Vec<[u32; 3]> = Vec::new();
for tri in triangles {
if kept.iter().any(|existing| overlaps(*existing, tri, points)) {
continue;
}
kept.push(tri);
}
if kept.is_empty() {
return Err(GeomError::Degenerate(
"no valid triangle survives the constraints".into(),
));
}
for &(start, end) in constraints {
let present = kept.iter().any(|tri| {
[(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])]
.iter()
.any(|&(u, v)| (u.min(v), u.max(v)) == (start, end))
});
if !present {
return Err(GeomError::Unsupported {
backend: axiolid_contracts::BackendId::new("scalar-retriangulate"),
operation: axiolid_contracts::Operation::MeshBoolean,
});
}
}
Ok(kept)
}
fn point_inside(point: Point2, [a, b, c]: [Point2; 3]) -> bool {
let signs = [
sign(orient2d(a, b, point)),
sign(orient2d(b, c, point)),
sign(orient2d(c, a, point)),
];
signs.iter().all(|&s| s == Sign::Positive) || signs.iter().all(|&s| s == Sign::Negative)
}
fn crosses_a_constraint(tri: [u32; 3], points: &[Point2], constraints: &[(u32, u32)]) -> bool {
let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
for &(u, v) in &edges {
for &(s, e) in constraints {
if u == s || u == e || v == s || v == e {
continue;
}
if segments_properly_cross(
[points[u as usize], points[v as usize]],
[points[s as usize], points[e as usize]],
) {
return true;
}
}
}
false
}
fn segments_properly_cross([a, b]: [Point2; 2], [c, d]: [Point2; 2]) -> bool {
let d1 = sign(orient2d(a, b, c));
let d2 = sign(orient2d(a, b, d));
let d3 = sign(orient2d(c, d, a));
let d4 = sign(orient2d(c, d, b));
d1 != Sign::Zero
&& d2 != Sign::Zero
&& d3 != Sign::Zero
&& d4 != Sign::Zero
&& d1 != d2
&& d3 != d4
}
fn overlaps(first: [u32; 3], second: [u32; 3], points: &[Point2]) -> bool {
let fa = first.map(|i| points[i as usize]);
let sa = second.map(|i| points[i as usize]);
if point_inside(centroid(fa), sa) || point_inside(centroid(sa), fa) {
return true;
}
for i in 0..3 {
for j in 0..3 {
let first_edge = [fa[i], fa[(i + 1) % 3]];
let second_edge = [sa[j], sa[(j + 1) % 3]];
if segments_properly_cross(first_edge, second_edge) {
return true;
}
}
}
false
}
fn centroid([a, b, c]: [Point2; 3]) -> Point2 {
Point2::new((a.x + b.x + c.x) / 3.0, (a.y + b.y + c.y) / 3.0)
}
fn sign(value: axiolid_contracts::Certified) -> Sign {
value.sign().expect("certified predicates are total")
}
fn point_on_face_edge(point: Point3, corners: [Point3; 3]) -> bool {
for i in 0..3 {
let a = corners[i];
let b = corners[(i + 1) % 3];
let ab = b - a;
let ap = point - a;
if ab.cross(ap).length_squared() != 0.0 {
continue;
}
let t = ab.dot(ap);
if t > 0.0 && t < ab.dot(ab) {
return true;
}
}
false
}