pub struct ResidualLoop {
pub nodes: Vec<NodeId>,
pub edges: Vec<EdgeId>,
}Expand description
A loop of the wavefront that stopped rather than collapsing: the offset
polygon a skeleton_constrained leaves behind.
§What it is
The wavefront of a plain skeleton always shrinks away to nothing — that
is what it means for the skeleton to be finished. Limits change that. Once
every edge around some loop has stopped, the loop stops too, and simply
stays there. What it stays as is the input polygon offset inward by the
limit: the flat left in the middle of a truncated roof.
So a constrained skeleton is not only the stubs reaching in from the boundary. It is those stubs and the outline they stop on, and this is that outline.
+-------------------------+ +-------------------------+
| | | \ / |
| | | +-----------------+ |
| | -> | | residual | |
| | | +-----------------+ |
| | | / \ |
+-------------------------+ +-------------------------+
every edge limited the arcs stop at the limit,
and this is where they stop§Why it is not made of Arcs
Because it would be a lie about what an Arc is. Every arc bisects the
supporting lines of exactly two input edges, which is what makes
Arc::sources meaningful and what the whole provenance story rests on. A
residual segment is parallel to one input edge and belongs to it alone.
Putting one in arcs would break the invariant every consumer of sources
relies on, so it lives here with the shape it actually has: each segment
names the one edge it came from.
§Winding
Inherited from the input, so the polygon’s interior stays on the left of every segment: the loop around the outer boundary runs counter-clockwise, and a loop around a hole runs clockwise.
§Examples
use straight_skeleton::{skeleton, skeleton_constrained, Point, Polygon};
let square = Polygon::from_outer(&[
Point::new(0, 0), Point::new(100, 0), Point::new(100, 100), Point::new(0, 100),
])?;
// Stop every edge at 20: what is left is the 60x60 square in the middle.
let skel = skeleton_constrained(&square, &[20.0; 4])?;
let flat = &skel.residual()[0];
assert_eq!(flat.len(), 4);
let mut corners: Vec<Point> = flat.nodes.iter().map(|&n| skel.node(n).position).collect();
corners.sort();
assert_eq!(corners, vec![
Point::new(20, 20), Point::new(20, 80), Point::new(80, 20), Point::new(80, 80),
]);
// An unconstrained skeleton has none: its wavefront always collapses.
assert!(skeleton(&square)?.residual().is_empty());Fields§
§nodes: Vec<NodeId>The loop’s corners, in wavefront order.
These are ordinary skeleton nodes — the far ends of the arcs that stopped
here — so their Node::offset is where the wavefront got to.
edges: Vec<EdgeId>The input edge each segment came from: edges[i] owns the segment from
nodes[i] to nodes[i + 1], wrapping at the end.
Always the same length as ResidualLoop::nodes. Every point on that
segment is min(offset, limit) from edges[i]’s supporting line, and
parallel to it.
Implementations§
Source§impl ResidualLoop
impl ResidualLoop
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Whether the loop is empty. It never is; this exists to satisfy the
convention that a type with len has is_empty.
Sourcepub fn segments(&self) -> impl Iterator<Item = (NodeId, NodeId, EdgeId)> + '_
pub fn segments(&self) -> impl Iterator<Item = (NodeId, NodeId, EdgeId)> + '_
The segments, as (from, to, source edge), in wavefront order.
§Examples
use straight_skeleton::{skeleton_constrained, Point, Polygon};
let square = Polygon::from_outer(&[
Point::new(0, 0), Point::new(100, 0), Point::new(100, 100), Point::new(0, 100),
])?;
let skel = skeleton_constrained(&square, &[20.0; 4])?;
// Four sides, each parallel to the wall it came from.
let sides: Vec<_> = skel.residual()[0].segments().collect();
assert_eq!(sides.len(), 4);Trait Implementations§
Source§impl Clone for ResidualLoop
impl Clone for ResidualLoop
Source§fn clone(&self) -> ResidualLoop
fn clone(&self) -> ResidualLoop
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more