use axiolid_contracts::{GeomError, GeomResult, Sign};
use axiolid_core::{Point2, Scalar};
use crate::orient2d;
pub fn signed_area2(ring: &[Point2]) -> Scalar {
let mut acc = 0.0;
for i in 0..ring.len() {
let a = ring[i];
let b = ring[(i + 1) % ring.len()];
acc += a.x * b.y - b.x * a.y;
}
acc
}
pub fn ring_orientation(ring: &[Point2]) -> Option<Sign> {
if ring.len() < 3 {
return None;
}
match signed_area2(ring) {
a if a > 0.0 => Some(Sign::Positive),
a if a < 0.0 => Some(Sign::Negative),
_ => None,
}
}
fn sign_of(a: Point2, b: Point2, c: Point2) -> Sign {
orient2d(a, b, c)
.sign()
.expect("orient2d escalates to exact arithmetic and always certifies")
}
fn blocks_ear(a: Point2, b: Point2, c: Point2, p: Point2) -> bool {
let s1 = sign_of(a, b, p);
let s2 = sign_of(b, c, p);
let s3 = sign_of(c, a, p);
s1 != Sign::Negative && s2 != Sign::Negative && s3 != Sign::Negative
}
fn is_ear(ring: &[Point2], indices: &[usize], at: usize) -> bool {
let n = indices.len();
let (ia, ib, ic) = (
indices[(at + n - 1) % n],
indices[at],
indices[(at + 1) % n],
);
let (a, b, c) = (ring[ia], ring[ib], ring[ic]);
if sign_of(a, b, c) != Sign::Positive {
return false;
}
!indices
.iter()
.filter(|&&idx| idx != ia && idx != ib && idx != ic)
.map(|&idx| ring[idx])
.filter(|q| *q != a && *q != b && *q != c)
.any(|q| blocks_ear(a, b, c, q))
}
pub fn triangulate_simple(ring: &[Point2]) -> GeomResult<Vec<[u32; 3]>> {
if ring.len() < 3 {
return Err(GeomError::Degenerate(format!(
"ring has {} vertices, need at least 3",
ring.len()
)));
}
let mut indices: Vec<usize> = (0..ring.len()).collect();
let mut out = Vec::with_capacity(ring.len().saturating_sub(2));
let mut at = 0usize;
let mut guard = 0usize;
while indices.len() > 3 {
if guard > indices.len() {
return Err(GeomError::Degenerate(
"no ear found; polygon is self-intersecting or degenerate".to_owned(),
));
}
if is_ear(ring, &indices, at % indices.len()) {
let n = indices.len();
let cur = at % n;
out.push([
indices[(cur + n - 1) % n] as u32,
indices[cur] as u32,
indices[(cur + 1) % n] as u32,
]);
indices.remove(cur);
at = cur;
guard = 0;
} else {
at += 1;
guard += 1;
}
}
let (a, b, c) = (ring[indices[0]], ring[indices[1]], ring[indices[2]]);
if sign_of(a, b, c) != Sign::Positive {
return Err(GeomError::Degenerate(
"final triangle is not positively wound; polygon is self-intersecting or degenerate"
.to_owned(),
));
}
out.push([indices[0] as u32, indices[1] as u32, indices[2] as u32]);
Ok(out)
}