use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::Point2;
use super::{by_value, locally_inside, segments_touch, Loop};
#[derive(Debug, Clone, Copy)]
pub(super) struct Node {
pub(super) vertex: u32,
pub(super) prev: usize,
pub(super) next: usize,
}
#[derive(Debug, Clone)]
pub(super) struct Polygon {
pub(super) nodes: Vec<Node>,
pub(super) start: usize,
pub(super) len: usize,
}
impl Polygon {
fn push_ring(&mut self, vertices: &[u32]) -> usize {
let first = self.nodes.len();
let n = vertices.len();
for (k, &vertex) in vertices.iter().enumerate() {
self.nodes.push(Node {
vertex,
prev: first + (k + n - 1) % n,
next: first + (k + 1) % n,
});
}
first
}
fn walk(&self) -> Vec<usize> {
let mut out = Vec::with_capacity(self.len);
let mut node = self.start;
for _ in 0..self.len {
out.push(node);
node = self.nodes[node].next;
}
out
}
fn splice(&mut self, p: usize, m: usize, hole_len: usize) {
let p_next = self.nodes[p].next;
let m_prev = self.nodes[m].prev;
let p2 = self.nodes.len();
let m2 = p2 + 1;
self.nodes.push(Node {
vertex: self.nodes[p].vertex,
prev: m2,
next: p_next,
});
self.nodes.push(Node {
vertex: self.nodes[m].vertex,
prev: m_prev,
next: p2,
});
self.nodes[p_next].prev = p2;
self.nodes[m_prev].next = m2;
self.nodes[p].next = m;
self.nodes[m].prev = p;
self.len += hole_len + 2;
}
}
pub(super) fn bridge_holes(points: &[Point2], loops: &[Loop]) -> GeomResult<Polygon> {
let at = |v: u32| points[v as usize];
let mut polygon = Polygon {
nodes: Vec::with_capacity(points.len() + 2 * loops.len()),
start: 0,
len: loops[0].vertices.len(),
};
polygon.push_ring(&loops[0].vertices);
let mut holes: Vec<(usize, usize)> = (1..loops.len())
.map(|h| {
let vertices = &loops[h].vertices;
let mut best = 0;
for (k, &v) in vertices.iter().enumerate() {
let (p, q) = (at(v), at(vertices[best]));
if p.x > q.x || (p.x == q.x && p.y > q.y) {
best = k;
}
}
(h, best)
})
.collect();
holes.sort_by(|&(h, k), &(g, j)| {
let (p, q) = (at(loops[h].vertices[k]), at(loops[g].vertices[j]));
by_value(q.x, p.x).then(by_value(q.y, p.y)).then(h.cmp(&g))
});
let mut pending: Vec<bool> = vec![true; loops.len()];
pending[0] = false;
for &(h, k) in &holes {
pending[h] = false;
let hole = &loops[h].vertices;
let n = hole.len();
let m = at(hole[k]);
let main = polygon.walk();
let mut edges: Vec<(Point2, Point2)> = main
.iter()
.map(|&i| {
let node = polygon.nodes[i];
(at(node.vertex), at(polygon.nodes[node.next].vertex))
})
.collect();
for (g, ring) in loops.iter().enumerate() {
if g == h || pending[g] {
let len = ring.vertices.len();
edges.extend(
(0..len).map(|i| (at(ring.vertices[i]), at(ring.vertices[(i + 1) % len]))),
);
}
}
let mut candidates = main.clone();
let distance = |i: usize| {
let p = at(polygon.nodes[i].vertex);
(p.x - m.x).powi(2) + (p.y - m.y).powi(2)
};
candidates.sort_by(|&i, &j| distance(i).total_cmp(&distance(j)).then(i.cmp(&j)));
let visits: Vec<usize> = (0..n).filter(|&j| hole[j] == hole[k]).collect();
let mut start = k;
let found = candidates.into_iter().find(|&i| {
let node = polygon.nodes[i];
let p = at(node.vertex);
let (p_prev, p_next) = (
at(polygon.nodes[node.prev].vertex),
at(polygon.nodes[node.next].vertex),
);
if !(locally_inside(p_prev, p, p_next, m) && clear(m, p, &edges)) {
return false;
}
if visits.len() == 1 {
return true;
}
let leaves =
|&j: &usize| locally_inside(at(hole[(j + n - 1) % n]), m, at(hole[(j + 1) % n]), p);
visits
.iter()
.find(|j| leaves(j))
.map(|&j| start = j)
.is_some()
});
let Some(p) = found else {
return Err(GeomError::Degenerate(format!(
"profile hole {} has no vertex it can be bridged to",
h - 1
)));
};
let first = polygon.push_ring(hole);
polygon.splice(p, first + start, n);
}
Ok(polygon)
}
fn clear(m: Point2, p: Point2, edges: &[(Point2, Point2)]) -> bool {
let (left, right) = (m.x.min(p.x), m.x.max(p.x));
let (low, high) = (m.y.min(p.y), m.y.max(p.y));
edges.iter().all(|&(a, b)| {
if a.x.max(b.x) < left || a.x.min(b.x) > right || a.y.max(b.y) < low || a.y.min(b.y) > high
{
return true;
}
let at_end = |q: Point2| q == m || q == p;
at_end(a) || at_end(b) || !segments_touch(m, p, a, b)
})
}