use axiolid_contracts::Sign;
use axiolid_core::Point2;
use axiolid_overlay::Polygon;
use axiolid_triangulate::{triangulate, Constraint};
use std::collections::HashMap;
use crate::{
contains, crosses, dedup_points, ring_edges, side, validate_region, within, RouteError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NodeKind {
End,
Path,
Junction,
Isolated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Wall {
pub polygon: usize,
pub ring: usize,
pub edge: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct SkeletonNode {
pub point: Point2,
pub kind: NodeKind,
pub clearance: (f64, f64),
pub ahead: Option<Wall>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Skeleton {
pub nodes: Vec<SkeletonNode>,
pub edges: Vec<(usize, usize)>,
pub spacing: f64,
}
impl Skeleton {
#[must_use]
pub fn ends(&self) -> Vec<usize> {
self.kinds(NodeKind::End)
}
#[must_use]
pub fn junctions(&self) -> Vec<usize> {
self.kinds(NodeKind::Junction)
}
fn kinds(&self, kind: NodeKind) -> Vec<usize> {
(0..self.nodes.len())
.filter(|&i| self.nodes[i].kind == kind)
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum SkeletonError {
Route(RouteError),
InvalidParameter,
Triangulation,
}
impl From<RouteError> for SkeletonError {
fn from(error: RouteError) -> Self {
Self::Route(error)
}
}
pub fn skeleton(region: &[Polygon], spacing: f64, prune: f64) -> Result<Skeleton, SkeletonError> {
if !(spacing.is_finite() && spacing > 0.0 && prune.is_finite() && prune >= 0.0) {
return Err(SkeletonError::InvalidParameter);
}
validate_region(region, &[])?;
let mut walls: Vec<(Wall, Point2, Point2)> = Vec::new();
for (pi, polygon) in region.iter().enumerate() {
for (ri, ring) in std::iter::once(&polygon.outer)
.chain(&polygon.holes)
.enumerate()
{
for (ei, (a, b)) in ring_edges(ring).into_iter().enumerate() {
if a != b {
let wall = Wall {
polygon: pi,
ring: ri,
edge: ei,
};
walls.push((wall, a, b));
}
}
}
}
let mut points: Vec<Point2> = Vec::new();
let mut pieces: Vec<(Point2, Point2)> = Vec::new();
for &(_, a, b) in &walls {
let count = ((b - a).length() / spacing).ceil().max(1.0) as usize;
let mut prev = a;
for k in 1..=count {
let q = if k == count {
b
} else {
a + (b - a) * (k as f64 / count as f64)
};
pieces.push((prev, q));
points.push(prev);
prev = q;
}
}
dedup_points(&mut points);
let mut index: HashMap<(u64, u64), u32> = HashMap::new();
for (i, p) in points.iter().enumerate() {
index.insert((p.x.to_bits(), p.y.to_bits()), i as u32);
}
let id = |p: Point2| index[&(p.x.to_bits(), p.y.to_bits())];
let mut constraints: Vec<Constraint> = pieces
.iter()
.map(|&(p, q)| Constraint::new(id(p), id(q)))
.collect();
constraints.sort_unstable();
constraints.dedup();
let tri = triangulate(&points, &constraints).map_err(|_| SkeletonError::Triangulation)?;
let at = tri.points();
let fixed: std::collections::BTreeSet<Constraint> = tri.constraints().iter().copied().collect();
let mut inside: Vec<[u32; 3]> = Vec::new();
for t in tri.triangles().chunks_exact(3) {
let (a, b, c) = (at[t[0] as usize], at[t[1] as usize], at[t[2] as usize]);
let centroid = Point2::new((a.x + b.x + c.x) / 3.0, (a.y + b.y + c.y) / 3.0);
if contains(region, centroid)? {
inside.push([t[0], t[1], t[2]]);
}
}
let mut nodes: Vec<Point2> = Vec::with_capacity(inside.len());
let mut by_edge: HashMap<(u32, u32), usize> = HashMap::new();
let mut links: Vec<(usize, usize)> = Vec::new();
for (k, t) in inside.iter().enumerate() {
let (a, b, c) = (at[t[0] as usize], at[t[1] as usize], at[t[2] as usize]);
nodes.push(circumcentre(a, b, c));
for e in 0..3 {
let (u, v) = (t[e], t[(e + 1) % 3]);
if fixed.contains(&Constraint::new(u, v)) {
continue;
}
match by_edge.entry((u.min(v), u.max(v))) {
std::collections::hash_map::Entry::Occupied(o) => links.push((*o.get(), k)),
std::collections::hash_map::Entry::Vacant(slot) => {
slot.insert(k);
}
}
}
}
let segments: Vec<(Point2, Point2)> = walls.iter().map(|&(_, a, b)| (a, b)).collect();
let clearance: Vec<(f64, f64)> = nodes.iter().map(|&p| clearance_of(p, &segments)).collect();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); nodes.len()];
for &(a, b) in &links {
if a != b && !adj[a].contains(&b) {
adj[a].push(b);
adj[b].push(a);
}
}
let alive: Vec<bool> = (0..nodes.len())
.map(|i| {
let c = clearance[i].1;
spread(nodes[i], c, 0.25 * c, &segments) >= prune * c
})
.collect();
for (i, list) in adj.iter_mut().enumerate() {
if !alive[i] {
list.clear();
} else {
list.retain(|&j| alive[j]);
}
}
let mut alive = alive;
loop {
let mut changed = false;
for end in 0..nodes.len() {
if !alive[end] || adj[end].len() != 1 {
continue;
}
let mut path = vec![end];
let mut length = 0.0;
let (mut prev, mut here) = (end, adj[end][0]);
while adj[here].len() == 2 {
length += (nodes[here] - nodes[prev]).length();
path.push(here);
let next = if adj[here][0] == prev {
adj[here][1]
} else {
adj[here][0]
};
prev = here;
here = next;
}
length += (nodes[here] - nodes[prev]).length();
if adj[here].len() >= 3 && length < clearance[here].1 {
for &n in &path {
alive[n] = false;
for o in std::mem::take(&mut adj[n]) {
adj[o].retain(|&x| x != n);
}
}
changed = true;
}
}
if !changed {
break;
}
}
if alive.iter().zip(&adj).any(|(&a, l)| a && !l.is_empty()) {
for i in 0..nodes.len() {
if adj[i].is_empty() {
alive[i] = false;
}
}
}
let mut renumber = vec![usize::MAX; nodes.len()];
let mut out_nodes = Vec::new();
for i in 0..nodes.len() {
if alive[i] && inside_exactly(region, nodes[i])? {
renumber[i] = out_nodes.len();
out_nodes.push(i);
}
}
let mut edges: Vec<(usize, usize)> = Vec::new();
for (i, list) in adj.iter().enumerate() {
for &j in list {
let (a, b) = (renumber[i], renumber[j]);
if i < j && a != usize::MAX && b != usize::MAX {
edges.push((a.min(b), a.max(b)));
}
}
}
edges.sort_unstable();
edges.dedup();
let mut degree = vec![0usize; out_nodes.len()];
for &(a, b) in &edges {
degree[a] += 1;
degree[b] += 1;
}
let mut result = Vec::with_capacity(out_nodes.len());
for (k, &i) in out_nodes.iter().enumerate() {
let kind = match degree[k] {
0 => NodeKind::Isolated,
1 => NodeKind::End,
2 => NodeKind::Path,
_ => NodeKind::Junction,
};
let ahead = if kind == NodeKind::End {
let here = nodes[i];
let (mut prev, mut at_node) = (usize::MAX, k);
let mut from = None;
for _ in 0..out_nodes.len() {
let next = edges.iter().find_map(|&(a, b)| {
let o = if a == at_node {
b
} else if b == at_node {
a
} else {
return None;
};
(o != prev).then_some(o)
});
let Some(next) = next else { break };
let p = nodes[out_nodes[next]];
if (p - here).length() >= 0.5 * clearance[i].0 {
from = Some(p);
break;
}
prev = at_node;
at_node = next;
}
match from {
Some(from) => wall_ahead(from, here, &walls)?,
None => None,
}
} else {
None
};
result.push(SkeletonNode {
point: nodes[i],
kind,
clearance: clearance[i],
ahead,
});
}
Ok(Skeleton {
nodes: result,
edges,
spacing,
})
}
fn circumcentre(a: Point2, b: Point2, c: Point2) -> Point2 {
let (u, v) = (b - a, c - a);
let d = 2.0 * u.perp_dot(v);
let (uu, vv) = (u.dot(u), v.dot(v));
a + Point2::new(v.y * uu - u.y * vv, u.x * vv - v.x * uu) / d
}
fn inside_exactly(region: &[Polygon], p: Point2) -> Result<bool, RouteError> {
for polygon in region {
if crate::map::in_polygon(polygon, p)? {
return Ok(true);
}
}
Ok(false)
}
fn wall_ahead(
from: Point2,
to: Point2,
walls: &[(Wall, Point2, Point2)],
) -> Result<Option<Wall>, RouteError> {
let d = to - from;
if d.length() == 0.0 {
return Ok(None);
}
let reach = walls
.iter()
.map(|&(_, a, b)| (a - to).length().max((b - to).length()))
.fold(0.0, f64::max);
let far = to + d * (4.0 * reach / d.length() + 1.0);
let mut best: Option<(f64, Wall)> = None;
for &(wall, a, b) in walls {
let hit = crosses(to, far, a, b)?
|| (side(to, far, a)? == Sign::Zero && within(to, far, a))
|| (side(to, far, b)? == Sign::Zero && within(to, far, b));
if !hit {
continue;
}
let e = b - a;
let den = d.perp_dot(e);
let t = if den == 0.0 {
(a - to).length()
} else {
(a - to).perp_dot(e) / den
};
if best.is_none_or(|(bt, _)| t < bt) {
best = Some((t, wall));
}
}
Ok(best.map(|(_, w)| w))
}
fn spread(p: Point2, clearance: f64, slack: f64, segments: &[(Point2, Point2)]) -> f64 {
let feet: Vec<Point2> = segments
.iter()
.filter_map(|&(a, b)| {
let e = b - a;
let t = ((p - a).dot(e) / e.dot(e)).clamp(0.0, 1.0);
let foot = a + e * t;
((p - foot).length() <= clearance + slack).then_some(foot)
})
.collect();
let mut widest = 0.0f64;
for (i, a) in feet.iter().enumerate() {
for b in &feet[i + 1..] {
widest = widest.max((*a - *b).length());
}
}
widest
}
fn clearance_of(p: Point2, segments: &[(Point2, Point2)]) -> (f64, f64) {
let mut low = f64::INFINITY;
let mut high = f64::INFINITY;
for &(a, b) in segments {
let (lo, hi) = segment_distance(p, a, b);
low = low.min(lo);
high = high.min(hi);
}
(low, high)
}
fn segment_distance(p: Point2, a: Point2, b: Point2) -> (f64, f64) {
let iv = |x: f64| Iv { lo: x, hi: x };
let (ex, ey) = (iv(b.x).sub(iv(a.x)), iv(b.y).sub(iv(a.y)));
let (wx, wy) = (iv(p.x).sub(iv(a.x)), iv(p.y).sub(iv(a.y)));
let dot = ex.mul(wx).add(ey.mul(wy));
let len2 = ex.mul(ex).add(ey.mul(ey));
let w2 = wx.mul(wx).add(wy.mul(wy));
let (vx, vy) = (iv(p.x).sub(iv(b.x)), iv(p.y).sub(iv(b.y)));
let v2 = vx.mul(vx).add(vy.mul(vy));
let perp = || {
let c = ex.mul(wy).sub(ey.mul(wx));
c.mul(c).div(len2)
};
let d2 = if dot.hi <= 0.0 {
w2
} else if dot.lo >= len2.hi {
v2
} else if dot.lo > 0.0 && dot.hi < len2.lo {
perp()
} else {
let q = perp();
Iv {
lo: q.lo.min(w2.lo).min(v2.lo),
hi: q.hi.max(w2.hi).max(v2.hi),
}
};
let lo = d2.lo.max(0.0).sqrt().next_down().max(0.0);
let hi = d2.hi.max(0.0).sqrt().next_up();
(lo, hi)
}
#[derive(Debug, Clone, Copy)]
struct Iv {
lo: f64,
hi: f64,
}
impl Iv {
fn outward(lo: f64, hi: f64) -> Self {
Self {
lo: lo.next_down(),
hi: hi.next_up(),
}
}
fn add(self, o: Self) -> Self {
Self::outward(self.lo + o.lo, self.hi + o.hi)
}
fn sub(self, o: Self) -> Self {
Self::outward(self.lo - o.hi, self.hi - o.lo)
}
fn mul(self, o: Self) -> Self {
let p = [
self.lo * o.lo,
self.lo * o.hi,
self.hi * o.lo,
self.hi * o.hi,
];
Self::outward(
p.iter().copied().fold(f64::INFINITY, f64::min),
p.iter().copied().fold(f64::NEG_INFINITY, f64::max),
)
}
fn div(self, o: Self) -> Self {
if o.lo <= 0.0 {
return Self {
lo: 0.0,
hi: f64::INFINITY,
};
}
let q = [
self.lo / o.lo,
self.lo / o.hi,
self.hi / o.lo,
self.hi / o.hi,
];
Self::outward(
q.iter().copied().fold(f64::INFINITY, f64::min),
q.iter().copied().fold(f64::NEG_INFINITY, f64::max),
)
}
}