use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::Point2;
use super::bridge::Polygon;
use super::orient;
pub(super) fn clip_ears(points: &[Point2], polygon: &mut Polygon) -> GeomResult<Vec<[u32; 3]>> {
let mut triangles = Vec::with_capacity(polygon.len.saturating_sub(2));
let mut grid = Grid::new(points, polygon);
let mut v = polygon.start;
let mut since_cut = 0;
while polygon.len > 3 {
let node = polygon.nodes[v];
if is_ear(points, polygon, &grid, v) {
let (a, b) = (node.prev, node.next);
triangles.push([
polygon.nodes[a].vertex,
node.vertex,
polygon.nodes[b].vertex,
]);
polygon.nodes[a].next = b;
polygon.nodes[b].prev = a;
polygon.len -= 1;
grid.remove(v);
since_cut = 0;
v = polygon.nodes[b].next;
} else {
since_cut += 1;
if since_cut > polygon.len {
return Err(GeomError::Degenerate(format!(
"profile triangulation found no ear among {} remaining vertices",
polygon.len
)));
}
v = node.next;
}
}
let node = polygon.nodes[v];
triangles.push([
polygon.nodes[node.prev].vertex,
node.vertex,
polygon.nodes[node.next].vertex,
]);
Ok(triangles)
}
fn is_ear(points: &[Point2], polygon: &Polygon, grid: &Grid, v: usize) -> bool {
let node = polygon.nodes[v];
let at = |vertex: u32| points[vertex as usize];
let (a, p, b) = (
at(polygon.nodes[node.prev].vertex),
at(node.vertex),
at(polygon.nodes[node.next].vertex),
);
if orient(a, p, b) <= 0 {
return false;
}
let low = Point2::new(a.x.min(p.x).min(b.x), a.y.min(p.y).min(b.y));
let high = Point2::new(a.x.max(p.x).max(b.x), a.y.max(p.y).max(b.y));
let blocked = grid.any_in(low, high, |other| {
let q = at(polygon.nodes[other].vertex);
if q.x < low.x || q.x > high.x || q.y < low.y || q.y > high.y {
return false;
}
if q == a || q == p || q == b {
return false;
}
orient(a, p, q) >= 0 && orient(p, b, q) >= 0 && orient(b, a, q) >= 0
});
!blocked
}
struct Grid {
origin: Point2,
cell: f64,
columns: usize,
rows: usize,
cells: Vec<Vec<usize>>,
slots: Vec<Option<(usize, usize)>>,
}
impl Grid {
fn new(points: &[Point2], polygon: &Polygon) -> Self {
let mut low = Point2::new(f64::INFINITY, f64::INFINITY);
let mut high = Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY);
for node in &polygon.nodes {
let p = points[node.vertex as usize];
low = Point2::new(low.x.min(p.x), low.y.min(p.y));
high = Point2::new(high.x.max(p.x), high.y.max(p.y));
}
let span = (high.x - low.x).max(high.y - low.y);
let per_side = (polygon.nodes.len() as f64).sqrt().ceil().max(1.0);
let cell = if span > 0.0 && span.is_finite() {
span / per_side
} else {
1.0
};
let count = |extent: f64| ((extent / cell).floor() as usize).saturating_add(1);
let (columns, rows) = (count(high.x - low.x), count(high.y - low.y));
let mut grid = Self {
origin: low,
cell,
columns,
rows,
cells: vec![Vec::new(); columns * rows],
slots: vec![None; polygon.nodes.len()],
};
for (index, node) in polygon.nodes.iter().enumerate() {
let p = points[node.vertex as usize];
let (prev, next) = (
points[polygon.nodes[node.prev].vertex as usize],
points[polygon.nodes[node.next].vertex as usize],
);
if orient(prev, p, next) > 0 {
continue;
}
let cell = grid.row(p.y) * columns + grid.column(p.x);
grid.slots[index] = Some((cell, grid.cells[cell].len()));
grid.cells[cell].push(index);
}
grid
}
fn remove(&mut self, node: usize) {
let Some((cell, position)) = self.slots[node].take() else {
return;
};
self.cells[cell].swap_remove(position);
if let Some(&moved) = self.cells[cell].get(position) {
self.slots[moved] = Some((cell, position));
}
}
fn column(&self, x: f64) -> usize {
(((x - self.origin.x) / self.cell).floor().max(0.0) as usize).min(self.columns - 1)
}
fn row(&self, y: f64) -> usize {
(((y - self.origin.y) / self.cell).floor().max(0.0) as usize).min(self.rows - 1)
}
fn any_in(&self, low: Point2, high: Point2, mut hit: impl FnMut(usize) -> bool) -> bool {
let (c0, c1) = (self.column(low.x), self.column(high.x));
let (r0, r1) = (self.row(low.y), self.row(high.y));
for r in r0..=r1 {
for c in c0..=c1 {
if self.cells[r * self.columns + c].iter().any(|&n| hit(n)) {
return true;
}
}
}
false
}
}