straight_skeleton/skeleton.rs
1//! The computed straight skeleton and its provenance queries.
2
3use alloc::vec;
4use alloc::vec::Vec;
5
6use crate::polygon::{EdgeId, VertexId};
7use crate::Point;
8
9/// Identifies a [`Node`] of a [`Skeleton`].
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct NodeId(pub u32);
13
14/// Identifies an [`Arc`] of a [`Skeleton`].
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct ArcId(pub u32);
18
19/// What produced a [`Node`].
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum NodeKind {
23 /// A node sitting on the input boundary, at offset 0. There is exactly one
24 /// of these per input vertex, and it carries that vertex's id.
25 Boundary(VertexId),
26 /// An interior node, created when the shrinking wavefront collapsed an edge
27 /// to nothing and two skeleton arcs met.
28 EdgeEvent,
29 /// An interior node, created when a reflex vertex ran into an opposing
30 /// wavefront edge and tore the wavefront into two loops.
31 SplitEvent,
32 /// A node where the wavefront stopped because every incident edge had
33 /// reached its per-edge distance limit.
34 ///
35 /// Only produced by [`skeleton_constrained`]; a plain skeleton never has
36 /// these.
37 ///
38 /// [`skeleton_constrained`]: crate::skeleton_constrained
39 LimitReached,
40}
41
42/// A vertex of the skeleton graph.
43///
44/// # Examples
45///
46/// ```
47/// use straight_skeleton::{skeleton, NodeKind, Point, Polygon};
48///
49/// let square = Polygon::from_outer(&[
50/// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
51/// ])?;
52/// let skel = skeleton(&square)?;
53///
54/// // Every input vertex gets a boundary node at offset 0.
55/// let boundary: Vec<_> = skel.nodes().iter().filter(|n| n.is_boundary()).collect();
56/// assert_eq!(boundary.len(), 4);
57/// assert!(boundary.iter().all(|n| n.offset == 0.0));
58/// # Ok::<(), Box<dyn std::error::Error>>(())
59/// ```
60#[derive(Clone, Debug, PartialEq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62pub struct Node {
63 /// The node's position, rounded to the integer lattice.
64 ///
65 /// Skeleton nodes are generally irrational even for integer input, so this
66 /// is the nearest lattice point. Use [`Node::exact`] when the rounding
67 /// matters.
68 pub position: Point,
69 /// The node's unrounded position.
70 ///
71 /// The algorithm computes in `f32` throughout, so this is the value it
72 /// actually arrived at, not a narrowing of something wider.
73 pub exact: [f32; 2],
74 /// How far the wavefront had travelled when this node was created.
75 ///
76 /// For a plain [`skeleton`], this is the node's distance to the supporting
77 /// line of each of its [`Node::sources`]. It is the node's height on a
78 /// roof, and the offset at which the node appears on an offset curve.
79 ///
80 /// For a [`skeleton_constrained`], it is the wavefront's **time**, which is
81 /// no longer the same thing: an edge that stopped at `limit` stays `limit`
82 /// away however long the wavefront runs on. The distance to a source edge
83 /// `e`'s line is `min(offset, limit_e)`.
84 ///
85 /// [`skeleton`]: crate::skeleton
86 /// [`skeleton_constrained`]: crate::skeleton_constrained
87 pub offset: f32,
88 /// What produced this node.
89 pub kind: NodeKind,
90 /// The input edges whose wavefronts arrived here together.
91 ///
92 /// Always at least 2 entries, and 3 or more where several skeleton arcs
93 /// meet. Each one's supporting line is [`Node::offset`] away (see that
94 /// field for the constrained case).
95 ///
96 /// # This is not quite "nearest"
97 ///
98 /// For a **convex** polygon these really are the nearest input edges, since
99 /// there the straight skeleton coincides with the medial axis.
100 ///
101 /// Elsewhere they may not be, and the difference is the definition of a
102 /// straight skeleton rather than a wrinkle in this implementation. A
103 /// straight skeleton bisects edges' infinite **supporting lines**, which is
104 /// what keeps every arc straight. A medial axis bisects the nearest
105 /// **features**, and so grows parabolic arcs around reflex vertices. Around
106 /// a reflex corner the two part company: a plus-shape's centre is at offset
107 /// 5 from the four arms' walls, but the nearest input feature is a reflex
108 /// corner 7.07 away.
109 ///
110 /// So read `sources` as *"the input edges whose faces meet here"* — which is
111 /// the useful notion anyway, and the one a roof needs. See [`Arc::sources`].
112 pub sources: Vec<EdgeId>,
113}
114
115impl Node {
116 /// Whether this node lies on the input boundary.
117 #[inline]
118 pub fn is_boundary(&self) -> bool {
119 matches!(self.kind, NodeKind::Boundary(_))
120 }
121
122 /// The input vertex this node sits on, if it is a boundary node.
123 #[inline]
124 pub fn input_vertex(&self) -> Option<VertexId> {
125 match self.kind {
126 NodeKind::Boundary(v) => Some(v),
127 _ => None,
128 }
129 }
130}
131
132/// An edge of the skeleton graph: a straight segment traced by one wavefront
133/// vertex as it moved.
134///
135/// # Provenance
136///
137/// Every arc separates the [faces] of **exactly two input edges** — the two in
138/// [`Arc::sources`] — and every point along it is equidistant from those two
139/// edges' supporting lines. This is not an approximation or a post-hoc lookup:
140/// it is what an arc *is*. A wavefront vertex exists precisely where two
141/// shrinking edges meet, and it carries those two edge ids with it as it sweeps
142/// the arc out.
143///
144/// So tracing an arc back to the input it came from is a field access, at zero
145/// cost. See [`Node::sources`] for the one caveat: on a non-convex polygon
146/// these are the edges whose faces meet here, which is not always the same as
147/// the Euclidean-nearest edges.
148///
149/// [faces]: Skeleton::face
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152pub struct Arc {
153 /// The arc's two endpoints. `nodes[0]` is always the lower-offset end, so
154 /// arcs point "uphill", away from the boundary.
155 pub nodes: [NodeId; 2],
156 /// The two input edges whose faces this arc separates.
157 pub sources: [EdgeId; 2],
158}
159
160impl Arc {
161 /// The endpoint nearer the input boundary.
162 #[inline]
163 pub fn lower(&self) -> NodeId {
164 self.nodes[0]
165 }
166
167 /// The endpoint further from the input boundary.
168 #[inline]
169 pub fn upper(&self) -> NodeId {
170 self.nodes[1]
171 }
172
173 /// The endpoint that is not `n`, or `None` if `n` is not an endpoint.
174 #[inline]
175 pub fn other(&self, n: NodeId) -> Option<NodeId> {
176 if self.nodes[0] == n {
177 Some(self.nodes[1])
178 } else if self.nodes[1] == n {
179 Some(self.nodes[0])
180 } else {
181 None
182 }
183 }
184}
185
186/// A loop of the wavefront that stopped rather than collapsing: the offset
187/// polygon a [`skeleton_constrained`] leaves behind.
188///
189/// # What it is
190///
191/// The wavefront of a plain [`skeleton`] always shrinks away to nothing — that
192/// is what it means for the skeleton to be finished. Limits change that. Once
193/// every edge around some loop has stopped, the loop stops too, and simply
194/// stays there. What it stays as is the input polygon offset inward by the
195/// limit: the *flat* left in the middle of a truncated roof.
196///
197/// So a constrained skeleton is not only the stubs reaching in from the
198/// boundary. It is those stubs **and** the outline they stop on, and this is
199/// that outline.
200///
201/// ```text
202/// +-------------------------+ +-------------------------+
203/// | | | \ / |
204/// | | | +-----------------+ |
205/// | | -> | | residual | |
206/// | | | +-----------------+ |
207/// | | | / \ |
208/// +-------------------------+ +-------------------------+
209/// every edge limited the arcs stop at the limit,
210/// and this is where they stop
211/// ```
212///
213/// # Why it is not made of [`Arc`]s
214///
215/// Because it would be a lie about what an `Arc` is. Every arc bisects the
216/// supporting lines of exactly two input edges, which is what makes
217/// [`Arc::sources`] meaningful and what the whole provenance story rests on. A
218/// residual segment is *parallel* to one input edge and belongs to it alone.
219/// Putting one in `arcs` would break the invariant every consumer of `sources`
220/// relies on, so it lives here with the shape it actually has: each segment
221/// names the one edge it came from.
222///
223/// # Winding
224///
225/// Inherited from the input, so the polygon's interior stays on the left of
226/// every segment: the loop around the outer boundary runs counter-clockwise,
227/// and a loop around a hole runs clockwise.
228///
229/// [`skeleton`]: crate::skeleton
230/// [`skeleton_constrained`]: crate::skeleton_constrained
231///
232/// # Examples
233///
234/// ```
235/// use straight_skeleton::{skeleton, skeleton_constrained, Point, Polygon};
236///
237/// let square = Polygon::from_outer(&[
238/// Point::new(0, 0), Point::new(100, 0), Point::new(100, 100), Point::new(0, 100),
239/// ])?;
240///
241/// // Stop every edge at 20: what is left is the 60x60 square in the middle.
242/// let skel = skeleton_constrained(&square, &[20.0; 4])?;
243/// let flat = &skel.residual()[0];
244/// assert_eq!(flat.len(), 4);
245///
246/// let mut corners: Vec<Point> = flat.nodes.iter().map(|&n| skel.node(n).position).collect();
247/// corners.sort();
248/// assert_eq!(corners, vec![
249/// Point::new(20, 20), Point::new(20, 80), Point::new(80, 20), Point::new(80, 80),
250/// ]);
251///
252/// // An unconstrained skeleton has none: its wavefront always collapses.
253/// assert!(skeleton(&square)?.residual().is_empty());
254/// # Ok::<(), Box<dyn std::error::Error>>(())
255/// ```
256#[derive(Clone, Debug, PartialEq, Eq)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
258pub struct ResidualLoop {
259 /// The loop's corners, in wavefront order.
260 ///
261 /// These are ordinary skeleton nodes — the far ends of the arcs that stopped
262 /// here — so their [`Node::offset`] is where the wavefront got to.
263 pub nodes: Vec<NodeId>,
264 /// The input edge each segment came from: `edges[i]` owns the segment from
265 /// `nodes[i]` to `nodes[i + 1]`, wrapping at the end.
266 ///
267 /// Always the same length as [`ResidualLoop::nodes`]. Every point on that
268 /// segment is `min(offset, limit)` from `edges[i]`'s supporting line, and
269 /// parallel to it.
270 pub edges: Vec<EdgeId>,
271}
272
273impl ResidualLoop {
274 /// How many corners, and so also how many segments.
275 #[inline]
276 pub fn len(&self) -> usize {
277 self.nodes.len()
278 }
279
280 /// Whether the loop is empty. It never is; this exists to satisfy the
281 /// convention that a type with `len` has `is_empty`.
282 #[inline]
283 pub fn is_empty(&self) -> bool {
284 self.nodes.is_empty()
285 }
286
287 /// The segments, as `(from, to, source edge)`, in wavefront order.
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// use straight_skeleton::{skeleton_constrained, Point, Polygon};
293 ///
294 /// let square = Polygon::from_outer(&[
295 /// Point::new(0, 0), Point::new(100, 0), Point::new(100, 100), Point::new(0, 100),
296 /// ])?;
297 /// let skel = skeleton_constrained(&square, &[20.0; 4])?;
298 ///
299 /// // Four sides, each parallel to the wall it came from.
300 /// let sides: Vec<_> = skel.residual()[0].segments().collect();
301 /// assert_eq!(sides.len(), 4);
302 /// # Ok::<(), Box<dyn std::error::Error>>(())
303 /// ```
304 pub fn segments(&self) -> impl Iterator<Item = (NodeId, NodeId, EdgeId)> + '_ {
305 (0..self.nodes.len()).map(move |i| {
306 (
307 self.nodes[i],
308 self.nodes[(i + 1) % self.nodes.len()],
309 self.edges[i],
310 )
311 })
312 }
313}
314
315/// The straight skeleton of a [`Polygon`].
316///
317/// A skeleton is a planar graph of [`Node`]s joined by [`Arc`]s. Build one with
318/// [`skeleton`] or [`skeleton_constrained`].
319///
320/// [`Polygon`]: crate::Polygon
321/// [`skeleton`]: crate::skeleton
322/// [`skeleton_constrained`]: crate::skeleton_constrained
323///
324/// # Examples
325///
326/// ```
327/// use straight_skeleton::{skeleton, Point, Polygon};
328///
329/// // A 10x10 square's skeleton is an X: four boundary nodes, one centre node,
330/// // four arcs running corner to centre.
331/// let square = Polygon::from_outer(&[
332/// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
333/// ])?;
334/// let skel = skeleton(&square)?;
335///
336/// assert_eq!(skel.node_count(), 5);
337/// assert_eq!(skel.arc_count(), 4);
338///
339/// // The interior node is the centre, at offset 5 (half the width).
340/// let centre = skel.nodes().iter().find(|n| !n.is_boundary()).unwrap();
341/// assert_eq!(centre.position, Point::new(5, 5));
342/// assert!((centre.offset - 5.0).abs() < 1e-4);
343///
344/// // ...and it is equidistant from all four input edges.
345/// assert_eq!(centre.sources.len(), 4);
346/// # Ok::<(), Box<dyn std::error::Error>>(())
347/// ```
348#[derive(Clone, Debug, PartialEq, Default)]
349#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
350pub struct Skeleton {
351 pub(crate) nodes: Vec<Node>,
352 pub(crate) arcs: Vec<Arc>,
353 /// `node_arcs[i]` lists the arcs incident to node `i`. Built once at the
354 /// end of the algorithm so that traversal queries are O(degree).
355 pub(crate) node_arcs: Vec<Vec<ArcId>>,
356 /// `edge_nodes[i]` is the pair of boundary nodes at input edge `i`'s start
357 /// and end vertices, which is where [`Skeleton::face`] begins its walk.
358 pub(crate) edge_nodes: Vec<[NodeId; 2]>,
359 /// The wavefront loops that stopped instead of collapsing. Empty unless
360 /// per-edge limits bound.
361 pub(crate) residual: Vec<ResidualLoop>,
362}
363
364impl Skeleton {
365 /// All nodes.
366 #[inline]
367 pub fn nodes(&self) -> &[Node] {
368 &self.nodes
369 }
370
371 /// All arcs.
372 #[inline]
373 pub fn arcs(&self) -> &[Arc] {
374 &self.arcs
375 }
376
377 /// The wavefront loops that stopped rather than collapsing — the offset
378 /// polygon a constrained skeleton leaves behind.
379 ///
380 /// **Empty for a plain [`skeleton`]**, whose wavefront always shrinks away
381 /// to nothing. Non-empty only where [`skeleton_constrained`]'s limits bound
382 /// hard enough to stop a whole loop, and then there is one entry per loop
383 /// still standing: the outer offset outline, plus one around each hole that
384 /// survived.
385 ///
386 /// This is the other half of a constrained result. The [`arcs`] are the
387 /// stubs reaching in from the boundary; this is the outline they stop on.
388 /// See [`ResidualLoop`] for why it is not made of arcs.
389 ///
390 /// [`skeleton`]: crate::skeleton
391 /// [`skeleton_constrained`]: crate::skeleton_constrained
392 /// [`arcs`]: Skeleton::arcs
393 ///
394 /// # Examples
395 ///
396 /// ```
397 /// use straight_skeleton::{skeleton_constrained, Point, Polygon};
398 ///
399 /// // An L-shape, every wall stopped at 20.
400 /// let l = Polygon::from_outer(&[
401 /// Point::new(0, 0), Point::new(200, 0), Point::new(200, 100),
402 /// Point::new(100, 100), Point::new(100, 200), Point::new(0, 200),
403 /// ])?;
404 /// let skel = skeleton_constrained(&l, &[20.0; 6])?;
405 ///
406 /// // What is left is the same L, 20 in from every wall.
407 /// assert_eq!(skel.residual().len(), 1);
408 /// let mut corners: Vec<Point> =
409 /// skel.residual()[0].nodes.iter().map(|&n| skel.node(n).position).collect();
410 /// corners.sort();
411 /// assert_eq!(corners, vec![
412 /// Point::new(20, 20), Point::new(20, 180), Point::new(80, 80),
413 /// Point::new(80, 180), Point::new(180, 20), Point::new(180, 80),
414 /// ]);
415 /// # Ok::<(), Box<dyn std::error::Error>>(())
416 /// ```
417 #[inline]
418 pub fn residual(&self) -> &[ResidualLoop] {
419 &self.residual
420 }
421
422 /// Number of nodes.
423 #[inline]
424 pub fn node_count(&self) -> usize {
425 self.nodes.len()
426 }
427
428 /// Number of arcs.
429 #[inline]
430 pub fn arc_count(&self) -> usize {
431 self.arcs.len()
432 }
433
434 /// Looks up a node.
435 ///
436 /// # Panics
437 ///
438 /// Panics if `n` does not belong to this skeleton.
439 #[inline]
440 pub fn node(&self, n: NodeId) -> &Node {
441 &self.nodes[n.0 as usize]
442 }
443
444 /// Looks up an arc.
445 ///
446 /// # Panics
447 ///
448 /// Panics if `a` does not belong to this skeleton.
449 #[inline]
450 pub fn arc(&self, a: ArcId) -> &Arc {
451 &self.arcs[a.0 as usize]
452 }
453
454 /// Iterates node ids.
455 pub fn node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
456 (0..self.nodes.len() as u32).map(NodeId)
457 }
458
459 /// Iterates arc ids.
460 pub fn arc_ids(&self) -> impl Iterator<Item = ArcId> + '_ {
461 (0..self.arcs.len() as u32).map(ArcId)
462 }
463
464 /// The arcs incident to a node.
465 ///
466 /// # Panics
467 ///
468 /// Panics if `n` does not belong to this skeleton.
469 #[inline]
470 pub fn arcs_at(&self, n: NodeId) -> &[ArcId] {
471 &self.node_arcs[n.0 as usize]
472 }
473
474 /// The arc's two endpoints, as positions.
475 ///
476 /// # Panics
477 ///
478 /// Panics if `a` does not belong to this skeleton.
479 #[inline]
480 pub fn arc_segment(&self, a: ArcId) -> (Point, Point) {
481 let arc = self.arc(a);
482 (
483 self.node(arc.nodes[0]).position,
484 self.node(arc.nodes[1]).position,
485 )
486 }
487
488 /// The two input edges a given arc came from.
489 ///
490 /// Every point along the arc is equidistant from these two edges'
491 /// supporting lines, and the arc separates their two faces. See [`Arc`] for
492 /// why this is exact rather than a search, and [`Node::sources`] for why
493 /// "came from" is more accurate than "closest to".
494 ///
495 /// # Examples
496 ///
497 /// ```
498 /// use straight_skeleton::{skeleton, Point, Polygon};
499 ///
500 /// let square = Polygon::from_outer(&[
501 /// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
502 /// ])?;
503 /// let skel = skeleton(&square)?;
504 ///
505 /// // Each of the square's four arcs bisects two adjacent input edges.
506 /// for a in skel.arc_ids() {
507 /// let [e0, e1] = skel.closest_input_edges(a);
508 /// assert_ne!(e0, e1);
509 /// }
510 /// # Ok::<(), Box<dyn std::error::Error>>(())
511 /// ```
512 ///
513 /// # Panics
514 ///
515 /// Panics if `a` does not belong to this skeleton.
516 #[inline]
517 pub fn closest_input_edges(&self, a: ArcId) -> [EdgeId; 2] {
518 self.arc(a).sources
519 }
520
521 /// The input edges a given node came from.
522 ///
523 /// See [`Node::sources`], which this returns.
524 ///
525 /// # Panics
526 ///
527 /// Panics if `n` does not belong to this skeleton.
528 #[inline]
529 pub fn closest_input_edges_to_node(&self, n: NodeId) -> &[EdgeId] {
530 &self.node(n).sources
531 }
532
533 /// The largest offset reached by any node: the radius of the largest disc
534 /// that fits inside the polygon.
535 ///
536 /// For a roof, this is the ridge height. Returns 0 for an empty skeleton.
537 pub fn max_offset(&self) -> f32 {
538 self.nodes.iter().map(|n| n.offset).fold(0.0, f32::max)
539 }
540
541 /// The boundary node sitting on a given input vertex.
542 ///
543 /// # Examples
544 ///
545 /// ```
546 /// use straight_skeleton::{skeleton, Point, Polygon, VertexId};
547 ///
548 /// let square = Polygon::from_outer(&[
549 /// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
550 /// ])?;
551 /// let skel = skeleton(&square)?;
552 ///
553 /// let n = skel.boundary_node(VertexId(2)).unwrap();
554 /// assert_eq!(skel.node(n).position, Point::new(10, 10));
555 /// # Ok::<(), Box<dyn std::error::Error>>(())
556 /// ```
557 pub fn boundary_node(&self, v: VertexId) -> Option<NodeId> {
558 // The algorithm emits one boundary node per input vertex, in order,
559 // before any interior node, so the ids line up.
560 let id = NodeId(v.0 as u32);
561 match self.nodes.get(v.0 as usize)?.kind {
562 NodeKind::Boundary(w) if w == v => Some(id),
563 _ => None,
564 }
565 }
566
567 /// The **face** of an input edge: the closed region the wavefront of that
568 /// edge swept out, as a loop of nodes.
569 ///
570 /// Every input edge has exactly one face, and the faces tile the polygon.
571 /// The returned loop starts with the edge's own two endpoints, then follows
572 /// the skeleton arcs that bound the face back around. Each face is planar
573 /// when nodes are lifted to `z = offset`, which is exactly why a straight
574 /// skeleton builds roofs: **one face is one roof plane**. See the `roof`
575 /// example.
576 ///
577 /// Returns `None` if the face cannot be walked, which should not happen for
578 /// a skeleton of a valid polygon.
579 ///
580 /// # Examples
581 ///
582 /// ```
583 /// use straight_skeleton::{skeleton, EdgeId, Point, Polygon};
584 ///
585 /// // Each of a square's four edges has a triangular face running to the
586 /// // centre.
587 /// let square = Polygon::from_outer(&[
588 /// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
589 /// ])?;
590 /// let skel = skeleton(&square)?;
591 ///
592 /// let face = skel.face(EdgeId(0)).unwrap();
593 /// assert_eq!(face.len(), 3);
594 ///
595 /// // A rectangle's long edges get quadrilateral faces, because the ridge
596 /// // gives them a fourth corner.
597 /// let rect = Polygon::from_outer(&[
598 /// Point::new(0, 0), Point::new(20, 0), Point::new(20, 10), Point::new(0, 10),
599 /// ])?;
600 /// let skel = skeleton(&rect)?;
601 /// assert_eq!(skel.face(EdgeId(0)).unwrap().len(), 4); // long edge
602 /// assert_eq!(skel.face(EdgeId(1)).unwrap().len(), 3); // short edge
603 /// # Ok::<(), Box<dyn std::error::Error>>(())
604 /// ```
605 pub fn face(&self, e: EdgeId) -> Option<Vec<NodeId>> {
606 let [start, end] = *self.edge_nodes.get(e.0 as usize)?;
607 let mut loop_ = vec![start, end];
608
609 // Walk from the edge's far endpoint back to its near one, taking only
610 // arcs that this edge is a source of — those are precisely the arcs
611 // bounding its face.
612 let mut cur = end;
613 let mut came_from: Option<ArcId> = None;
614 loop {
615 let next_arc = self
616 .arcs_at(cur)
617 .iter()
618 .copied()
619 .find(|&a| Some(a) != came_from && self.arc(a).sources.contains(&e));
620
621 let other = match next_arc {
622 Some(a) => {
623 came_from = Some(a);
624 self.arc(a).other(cur)?
625 }
626 // No arc leads on, which on a plain skeleton means the walk is
627 // lost. On a constrained one it usually means the opposite: the
628 // wavefront stopped here rather than collapsing, so what bounds
629 // the face is not an arc at all but the residual segment `e`
630 // stopped as. Cross it and carry on.
631 None => {
632 came_from = None;
633 self.residual_step(e, cur)?
634 }
635 };
636
637 if other == start {
638 return Some(loop_);
639 }
640 loop_.push(other);
641 cur = other;
642 // A face cannot have more corners than the skeleton has nodes.
643 if loop_.len() > self.nodes.len() + 2 {
644 return None;
645 }
646 }
647 }
648
649 /// Crosses `e`'s residual segment, arriving at `cur`.
650 ///
651 /// Segments run in their edge's own direction, and [`Skeleton::face`] walks
652 /// a face the other way — from the edge's far end back to its near one — so
653 /// it always meets a segment at the `to` end and leaves by the `from` end.
654 fn residual_step(&self, e: EdgeId, cur: NodeId) -> Option<NodeId> {
655 self.residual
656 .iter()
657 .flat_map(|l| l.segments())
658 .find(|&(_, to, edge)| edge == e && to == cur)
659 .map(|(from, _, _)| from)
660 }
661
662 /// How many input edges the polygon had.
663 ///
664 /// Each one owns exactly one [`face`](Skeleton::face), so this is also the
665 /// number of faces.
666 ///
667 /// # Examples
668 ///
669 /// ```
670 /// use straight_skeleton::{skeleton, Point, Polygon};
671 ///
672 /// let square = Polygon::from_outer(&[
673 /// Point::new(0, 0), Point::new(10, 0), Point::new(10, 10), Point::new(0, 10),
674 /// ])?;
675 /// assert_eq!(skeleton(&square)?.input_edge_count(), 4);
676 /// # Ok::<(), Box<dyn std::error::Error>>(())
677 /// ```
678 #[inline]
679 pub fn input_edge_count(&self) -> usize {
680 self.edge_nodes.len()
681 }
682
683 /// Every input edge's face, in edge order.
684 ///
685 /// Returns `None` if any face cannot be walked.
686 pub fn faces(&self) -> Option<Vec<Vec<NodeId>>> {
687 (0..self.edge_nodes.len() as u16)
688 .map(|i| self.face(EdgeId(i)))
689 .collect()
690 }
691
692 /// Rebuilds the node-to-arc adjacency. Called once when the algorithm
693 /// finishes.
694 pub(crate) fn build_adjacency(&mut self) {
695 self.node_arcs.clear();
696 self.node_arcs.resize(self.nodes.len(), Vec::new());
697 for (i, arc) in self.arcs.iter().enumerate() {
698 let id = ArcId(i as u32);
699 self.node_arcs[arc.nodes[0].0 as usize].push(id);
700 // A degenerate zero-length arc would otherwise be listed twice.
701 if arc.nodes[0] != arc.nodes[1] {
702 self.node_arcs[arc.nodes[1].0 as usize].push(id);
703 }
704 }
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711 use alloc::vec;
712
713 fn node(kind: NodeKind, offset: f32) -> Node {
714 Node {
715 position: Point::ORIGIN,
716 exact: [0.0, 0.0],
717 offset,
718 kind,
719 sources: vec![EdgeId(0), EdgeId(1)],
720 }
721 }
722
723 #[test]
724 fn arc_other_endpoint() {
725 let a = Arc {
726 nodes: [NodeId(1), NodeId(2)],
727 sources: [EdgeId(0), EdgeId(1)],
728 };
729 assert_eq!(a.other(NodeId(1)), Some(NodeId(2)));
730 assert_eq!(a.other(NodeId(2)), Some(NodeId(1)));
731 assert_eq!(a.other(NodeId(3)), None);
732 assert_eq!(a.lower(), NodeId(1));
733 assert_eq!(a.upper(), NodeId(2));
734 }
735
736 #[test]
737 fn node_kind_helpers() {
738 let b = node(NodeKind::Boundary(VertexId(7)), 0.0);
739 assert!(b.is_boundary());
740 assert_eq!(b.input_vertex(), Some(VertexId(7)));
741
742 let i = node(NodeKind::EdgeEvent, 3.0);
743 assert!(!i.is_boundary());
744 assert_eq!(i.input_vertex(), None);
745 }
746
747 #[test]
748 fn adjacency_lists_every_incident_arc() {
749 let mut s = Skeleton {
750 nodes: vec![
751 node(NodeKind::Boundary(VertexId(0)), 0.0),
752 node(NodeKind::Boundary(VertexId(1)), 0.0),
753 node(NodeKind::EdgeEvent, 5.0),
754 ],
755 arcs: vec![
756 Arc {
757 nodes: [NodeId(0), NodeId(2)],
758 sources: [EdgeId(0), EdgeId(1)],
759 },
760 Arc {
761 nodes: [NodeId(1), NodeId(2)],
762 sources: [EdgeId(1), EdgeId(2)],
763 },
764 ],
765 node_arcs: Vec::new(),
766 edge_nodes: Vec::new(),
767 residual: Vec::new(),
768 };
769 s.build_adjacency();
770
771 assert_eq!(s.arcs_at(NodeId(0)), &[ArcId(0)]);
772 assert_eq!(s.arcs_at(NodeId(1)), &[ArcId(1)]);
773 assert_eq!(s.arcs_at(NodeId(2)), &[ArcId(0), ArcId(1)]);
774 }
775
776 #[test]
777 fn max_offset_of_empty_skeleton_is_zero() {
778 assert_eq!(Skeleton::default().max_offset(), 0.0);
779 }
780
781 #[test]
782 fn max_offset_finds_the_ridge() {
783 let mut s = Skeleton {
784 nodes: vec![
785 node(NodeKind::Boundary(VertexId(0)), 0.0),
786 node(NodeKind::EdgeEvent, 5.0),
787 node(NodeKind::EdgeEvent, 2.0),
788 ],
789 arcs: vec![],
790 node_arcs: Vec::new(),
791 edge_nodes: Vec::new(),
792 residual: Vec::new(),
793 };
794 s.build_adjacency();
795 assert_eq!(s.max_offset(), 5.0);
796 }
797}