1use axiolid_contracts::Sign;
37use axiolid_core::Point2;
38use axiolid_overlay::Polygon;
39use axiolid_triangulate::{triangulate, Constraint};
40use std::collections::HashMap;
41
42use crate::{
43 contains, crosses, dedup_points, ring_edges, side, validate_region, within, RouteError,
44};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum NodeKind {
50 End,
52 Path,
54 Junction,
56 Isolated,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct Wall {
64 pub polygon: usize,
66 pub ring: usize,
68 pub edge: usize,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq)]
74#[non_exhaustive]
75pub struct SkeletonNode {
76 pub point: Point2,
78 pub kind: NodeKind,
80 pub clearance: (f64, f64),
82 pub ahead: Option<Wall>,
84}
85
86#[derive(Debug, Clone, PartialEq)]
88#[non_exhaustive]
89pub struct Skeleton {
90 pub nodes: Vec<SkeletonNode>,
92 pub edges: Vec<(usize, usize)>,
94 pub spacing: f64,
96}
97
98impl Skeleton {
99 #[must_use]
101 pub fn ends(&self) -> Vec<usize> {
102 self.kinds(NodeKind::End)
103 }
104
105 #[must_use]
107 pub fn junctions(&self) -> Vec<usize> {
108 self.kinds(NodeKind::Junction)
109 }
110
111 fn kinds(&self, kind: NodeKind) -> Vec<usize> {
112 (0..self.nodes.len())
113 .filter(|&i| self.nodes[i].kind == kind)
114 .collect()
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq)]
120#[non_exhaustive]
121pub enum SkeletonError {
122 Route(RouteError),
124 InvalidParameter,
126 Triangulation,
128}
129
130impl From<RouteError> for SkeletonError {
131 fn from(error: RouteError) -> Self {
132 Self::Route(error)
133 }
134}
135
136pub fn skeleton(region: &[Polygon], spacing: f64, prune: f64) -> Result<Skeleton, SkeletonError> {
145 if !(spacing.is_finite() && spacing > 0.0 && prune.is_finite() && prune >= 0.0) {
146 return Err(SkeletonError::InvalidParameter);
147 }
148 validate_region(region, &[])?;
149 let mut walls: Vec<(Wall, Point2, Point2)> = Vec::new();
151 for (pi, polygon) in region.iter().enumerate() {
152 for (ri, ring) in std::iter::once(&polygon.outer)
153 .chain(&polygon.holes)
154 .enumerate()
155 {
156 for (ei, (a, b)) in ring_edges(ring).into_iter().enumerate() {
157 if a != b {
158 let wall = Wall {
159 polygon: pi,
160 ring: ri,
161 edge: ei,
162 };
163 walls.push((wall, a, b));
164 }
165 }
166 }
167 }
168 let mut points: Vec<Point2> = Vec::new();
169 let mut pieces: Vec<(Point2, Point2)> = Vec::new();
170 for &(_, a, b) in &walls {
171 let count = ((b - a).length() / spacing).ceil().max(1.0) as usize;
172 let mut prev = a;
173 for k in 1..=count {
174 let q = if k == count {
175 b
176 } else {
177 a + (b - a) * (k as f64 / count as f64)
178 };
179 pieces.push((prev, q));
180 points.push(prev);
181 prev = q;
182 }
183 }
184 dedup_points(&mut points);
185 let mut index: HashMap<(u64, u64), u32> = HashMap::new();
186 for (i, p) in points.iter().enumerate() {
187 index.insert((p.x.to_bits(), p.y.to_bits()), i as u32);
188 }
189 let id = |p: Point2| index[&(p.x.to_bits(), p.y.to_bits())];
190 let mut constraints: Vec<Constraint> = pieces
191 .iter()
192 .map(|&(p, q)| Constraint::new(id(p), id(q)))
193 .collect();
194 constraints.sort_unstable();
195 constraints.dedup();
196 let tri = triangulate(&points, &constraints).map_err(|_| SkeletonError::Triangulation)?;
197 let at = tri.points();
198 let fixed: std::collections::BTreeSet<Constraint> = tri.constraints().iter().copied().collect();
199 let mut inside: Vec<[u32; 3]> = Vec::new();
201 for t in tri.triangles().chunks_exact(3) {
202 let (a, b, c) = (at[t[0] as usize], at[t[1] as usize], at[t[2] as usize]);
203 let centroid = Point2::new((a.x + b.x + c.x) / 3.0, (a.y + b.y + c.y) / 3.0);
204 if contains(region, centroid)? {
205 inside.push([t[0], t[1], t[2]]);
206 }
207 }
208 let mut nodes: Vec<Point2> = Vec::with_capacity(inside.len());
212 let mut by_edge: HashMap<(u32, u32), usize> = HashMap::new();
213 let mut links: Vec<(usize, usize)> = Vec::new();
214 for (k, t) in inside.iter().enumerate() {
215 let (a, b, c) = (at[t[0] as usize], at[t[1] as usize], at[t[2] as usize]);
216 nodes.push(circumcentre(a, b, c));
217 for e in 0..3 {
218 let (u, v) = (t[e], t[(e + 1) % 3]);
219 if fixed.contains(&Constraint::new(u, v)) {
220 continue;
221 }
222 match by_edge.entry((u.min(v), u.max(v))) {
223 std::collections::hash_map::Entry::Occupied(o) => links.push((*o.get(), k)),
224 std::collections::hash_map::Entry::Vacant(slot) => {
225 slot.insert(k);
226 }
227 }
228 }
229 }
230 let segments: Vec<(Point2, Point2)> = walls.iter().map(|&(_, a, b)| (a, b)).collect();
231 let clearance: Vec<(f64, f64)> = nodes.iter().map(|&p| clearance_of(p, &segments)).collect();
232 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); nodes.len()];
234 for &(a, b) in &links {
235 if a != b && !adj[a].contains(&b) {
236 adj[a].push(b);
237 adj[b].push(a);
238 }
239 }
240 let alive: Vec<bool> = (0..nodes.len())
245 .map(|i| {
246 let c = clearance[i].1;
247 spread(nodes[i], c, 0.25 * c, &segments) >= prune * c
251 })
252 .collect();
253 for (i, list) in adj.iter_mut().enumerate() {
254 if !alive[i] {
255 list.clear();
256 } else {
257 list.retain(|&j| alive[j]);
258 }
259 }
260 let mut alive = alive;
263 loop {
264 let mut changed = false;
265 for end in 0..nodes.len() {
266 if !alive[end] || adj[end].len() != 1 {
267 continue;
268 }
269 let mut path = vec![end];
270 let mut length = 0.0;
271 let (mut prev, mut here) = (end, adj[end][0]);
272 while adj[here].len() == 2 {
273 length += (nodes[here] - nodes[prev]).length();
274 path.push(here);
275 let next = if adj[here][0] == prev {
276 adj[here][1]
277 } else {
278 adj[here][0]
279 };
280 prev = here;
281 here = next;
282 }
283 length += (nodes[here] - nodes[prev]).length();
284 if adj[here].len() >= 3 && length < clearance[here].1 {
285 for &n in &path {
286 alive[n] = false;
287 for o in std::mem::take(&mut adj[n]) {
288 adj[o].retain(|&x| x != n);
289 }
290 }
291 changed = true;
292 }
293 }
294 if !changed {
295 break;
296 }
297 }
298 if alive.iter().zip(&adj).any(|(&a, l)| a && !l.is_empty()) {
300 for i in 0..nodes.len() {
301 if adj[i].is_empty() {
302 alive[i] = false;
303 }
304 }
305 }
306 let mut renumber = vec![usize::MAX; nodes.len()];
308 let mut out_nodes = Vec::new();
309 for i in 0..nodes.len() {
310 if alive[i] && inside_exactly(region, nodes[i])? {
311 renumber[i] = out_nodes.len();
312 out_nodes.push(i);
313 }
314 }
315 let mut edges: Vec<(usize, usize)> = Vec::new();
316 for (i, list) in adj.iter().enumerate() {
317 for &j in list {
318 let (a, b) = (renumber[i], renumber[j]);
319 if i < j && a != usize::MAX && b != usize::MAX {
320 edges.push((a.min(b), a.max(b)));
321 }
322 }
323 }
324 edges.sort_unstable();
325 edges.dedup();
326 let mut degree = vec![0usize; out_nodes.len()];
327 for &(a, b) in &edges {
328 degree[a] += 1;
329 degree[b] += 1;
330 }
331 let mut result = Vec::with_capacity(out_nodes.len());
332 for (k, &i) in out_nodes.iter().enumerate() {
333 let kind = match degree[k] {
334 0 => NodeKind::Isolated,
335 1 => NodeKind::End,
336 2 => NodeKind::Path,
337 _ => NodeKind::Junction,
338 };
339 let ahead = if kind == NodeKind::End {
340 let here = nodes[i];
344 let (mut prev, mut at_node) = (usize::MAX, k);
345 let mut from = None;
346 for _ in 0..out_nodes.len() {
347 let next = edges.iter().find_map(|&(a, b)| {
348 let o = if a == at_node {
349 b
350 } else if b == at_node {
351 a
352 } else {
353 return None;
354 };
355 (o != prev).then_some(o)
356 });
357 let Some(next) = next else { break };
358 let p = nodes[out_nodes[next]];
359 if (p - here).length() >= 0.5 * clearance[i].0 {
360 from = Some(p);
361 break;
362 }
363 prev = at_node;
364 at_node = next;
365 }
366 match from {
367 Some(from) => wall_ahead(from, here, &walls)?,
368 None => None,
369 }
370 } else {
371 None
372 };
373 result.push(SkeletonNode {
374 point: nodes[i],
375 kind,
376 clearance: clearance[i],
377 ahead,
378 });
379 }
380 Ok(Skeleton {
381 nodes: result,
382 edges,
383 spacing,
384 })
385}
386
387fn circumcentre(a: Point2, b: Point2, c: Point2) -> Point2 {
389 let (u, v) = (b - a, c - a);
390 let d = 2.0 * u.perp_dot(v);
391 let (uu, vv) = (u.dot(u), v.dot(v));
392 a + Point2::new(v.y * uu - u.y * vv, u.x * vv - v.x * uu) / d
393}
394
395fn inside_exactly(region: &[Polygon], p: Point2) -> Result<bool, RouteError> {
397 for polygon in region {
398 if crate::map::in_polygon(polygon, p)? {
399 return Ok(true);
400 }
401 }
402 Ok(false)
403}
404
405fn wall_ahead(
408 from: Point2,
409 to: Point2,
410 walls: &[(Wall, Point2, Point2)],
411) -> Result<Option<Wall>, RouteError> {
412 let d = to - from;
413 if d.length() == 0.0 {
414 return Ok(None);
415 }
416 let reach = walls
417 .iter()
418 .map(|&(_, a, b)| (a - to).length().max((b - to).length()))
419 .fold(0.0, f64::max);
420 let far = to + d * (4.0 * reach / d.length() + 1.0);
421 let mut best: Option<(f64, Wall)> = None;
422 for &(wall, a, b) in walls {
423 let hit = crosses(to, far, a, b)?
425 || (side(to, far, a)? == Sign::Zero && within(to, far, a))
426 || (side(to, far, b)? == Sign::Zero && within(to, far, b));
427 if !hit {
428 continue;
429 }
430 let e = b - a;
434 let den = d.perp_dot(e);
435 let t = if den == 0.0 {
436 (a - to).length()
437 } else {
438 (a - to).perp_dot(e) / den
439 };
440 if best.is_none_or(|(bt, _)| t < bt) {
441 best = Some((t, wall));
442 }
443 }
444 Ok(best.map(|(_, w)| w))
445}
446
447fn spread(p: Point2, clearance: f64, slack: f64, segments: &[(Point2, Point2)]) -> f64 {
450 let feet: Vec<Point2> = segments
451 .iter()
452 .filter_map(|&(a, b)| {
453 let e = b - a;
454 let t = ((p - a).dot(e) / e.dot(e)).clamp(0.0, 1.0);
455 let foot = a + e * t;
456 ((p - foot).length() <= clearance + slack).then_some(foot)
457 })
458 .collect();
459 let mut widest = 0.0f64;
460 for (i, a) in feet.iter().enumerate() {
461 for b in &feet[i + 1..] {
462 widest = widest.max((*a - *b).length());
463 }
464 }
465 widest
466}
467
468fn clearance_of(p: Point2, segments: &[(Point2, Point2)]) -> (f64, f64) {
470 let mut low = f64::INFINITY;
471 let mut high = f64::INFINITY;
472 for &(a, b) in segments {
473 let (lo, hi) = segment_distance(p, a, b);
474 low = low.min(lo);
475 high = high.min(hi);
476 }
477 (low, high)
478}
479
480fn segment_distance(p: Point2, a: Point2, b: Point2) -> (f64, f64) {
483 let iv = |x: f64| Iv { lo: x, hi: x };
484 let (ex, ey) = (iv(b.x).sub(iv(a.x)), iv(b.y).sub(iv(a.y)));
485 let (wx, wy) = (iv(p.x).sub(iv(a.x)), iv(p.y).sub(iv(a.y)));
486 let dot = ex.mul(wx).add(ey.mul(wy));
487 let len2 = ex.mul(ex).add(ey.mul(ey));
488 let w2 = wx.mul(wx).add(wy.mul(wy));
489 let (vx, vy) = (iv(p.x).sub(iv(b.x)), iv(p.y).sub(iv(b.y)));
490 let v2 = vx.mul(vx).add(vy.mul(vy));
491 let perp = || {
494 let c = ex.mul(wy).sub(ey.mul(wx));
495 c.mul(c).div(len2)
496 };
497 let d2 = if dot.hi <= 0.0 {
498 w2
499 } else if dot.lo >= len2.hi {
500 v2
501 } else if dot.lo > 0.0 && dot.hi < len2.lo {
502 perp()
503 } else {
504 let q = perp();
505 Iv {
506 lo: q.lo.min(w2.lo).min(v2.lo),
507 hi: q.hi.max(w2.hi).max(v2.hi),
508 }
509 };
510 let lo = d2.lo.max(0.0).sqrt().next_down().max(0.0);
511 let hi = d2.hi.max(0.0).sqrt().next_up();
512 (lo, hi)
513}
514
515#[derive(Debug, Clone, Copy)]
517struct Iv {
518 lo: f64,
519 hi: f64,
520}
521
522impl Iv {
523 fn outward(lo: f64, hi: f64) -> Self {
524 Self {
525 lo: lo.next_down(),
526 hi: hi.next_up(),
527 }
528 }
529
530 fn add(self, o: Self) -> Self {
531 Self::outward(self.lo + o.lo, self.hi + o.hi)
532 }
533
534 fn sub(self, o: Self) -> Self {
535 Self::outward(self.lo - o.hi, self.hi - o.lo)
536 }
537
538 fn mul(self, o: Self) -> Self {
539 let p = [
540 self.lo * o.lo,
541 self.lo * o.hi,
542 self.hi * o.lo,
543 self.hi * o.hi,
544 ];
545 Self::outward(
546 p.iter().copied().fold(f64::INFINITY, f64::min),
547 p.iter().copied().fold(f64::NEG_INFINITY, f64::max),
548 )
549 }
550
551 fn div(self, o: Self) -> Self {
553 if o.lo <= 0.0 {
554 return Self {
555 lo: 0.0,
556 hi: f64::INFINITY,
557 };
558 }
559 let q = [
560 self.lo / o.lo,
561 self.lo / o.hi,
562 self.hi / o.lo,
563 self.hi / o.hi,
564 ];
565 Self::outward(
566 q.iter().copied().fold(f64::INFINITY, f64::min),
567 q.iter().copied().fold(f64::NEG_INFINITY, f64::max),
568 )
569 }
570}