mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
219 lines
8.1 KiB
Rust
219 lines
8.1 KiB
Rust
use super::index::{RectIndex, UNSHEAR, segment_footprint};
|
|
use super::*;
|
|
|
|
/// The inputs' segments (in the sweep's frame), to tell which inputs an edge lies on.
|
|
pub(super) struct Outlines {
|
|
segs: RectIndex<(PathSeg, usize)>,
|
|
tol: f64,
|
|
}
|
|
|
|
impl Outlines {
|
|
pub(super) fn new(inputs: &[(BezPath, usize)], eps: f64) -> Self {
|
|
// A zero-length segment (a point) is the source of no edge, even one running through it.
|
|
let segs = inputs
|
|
.iter()
|
|
.flat_map(|(bp, i)| bp.segments().map(move |s| (s.bounding_box(), (s, *i))))
|
|
.filter(|(r, ..)| r.width() > 0.0 || r.height() > 0.0)
|
|
.collect();
|
|
// The sweep moves points by a few `eps` at most.
|
|
Self { segs: RectIndex::with_footprint(segs, |r, (s, _), cell| segment_footprint(r, s, cell)), tol: 16.0 * eps }
|
|
}
|
|
|
|
/// The front-most input whose outline passes through `p`.
|
|
pub(super) fn source_at(&self, p: Point) -> Option<usize> {
|
|
use kurbo::ParamCurveNearest;
|
|
let tol = self.tol;
|
|
self.segs.at(p, tol).filter(|(s, _)| s.nearest(p, 1e-9).distance_sq <= tol * tol).map(|(_, i)| *i).max()
|
|
}
|
|
}
|
|
|
|
/// The area an input's fill covers: its closed subpaths (open ones bound no face).
|
|
pub(super) struct Fill {
|
|
path: BezPath,
|
|
bounds: kurbo::Rect,
|
|
rule: FillRule,
|
|
}
|
|
|
|
impl Fill {
|
|
pub(super) fn new(s: &Shape) -> Self {
|
|
let mut path = BezPath::new();
|
|
for sp in s.path.subpaths.iter().filter(|sp| sp.closed && sp.anchors.len() >= 2) {
|
|
sp.to_bezpath_into(&mut path);
|
|
}
|
|
Self { bounds: path.bounding_box(), path, rule: s.rule }
|
|
}
|
|
|
|
pub(super) fn contains(&self, p: Point) -> bool {
|
|
!self.path.elements().is_empty() && self.bounds.contains(p) && crate::boolean::inside(self.rule, self.path.winding(p))
|
|
}
|
|
}
|
|
|
|
/// One output segment of the sweep: an edge of the planar graph between two vertices.
|
|
pub(super) struct Edge {
|
|
pub(super) segs: Vec<Seg>,
|
|
pub(super) ends: [usize; 2],
|
|
/// The input it came from (the front-most, where inputs share it).
|
|
pub(super) source: usize,
|
|
}
|
|
|
|
/// `segs` (a chain) moved to start at `a` and end at `b`, the handles next to each moving with it.
|
|
pub(crate) fn pin_ends(segs: &mut [Seg], a: Point, b: Point) {
|
|
if let Some(s) = segs.first_mut() {
|
|
let d = a - s.c.p0;
|
|
s.c.p0 = a;
|
|
s.c.p1 += d;
|
|
}
|
|
if let Some(s) = segs.last_mut() {
|
|
let d = b - s.c.p3;
|
|
s.c.p3 = b;
|
|
s.c.p2 += d;
|
|
}
|
|
}
|
|
|
|
/// Half-edge `2e` runs along edge `e`, `2e + 1` back along it.
|
|
pub(super) fn twin(h: usize) -> usize {
|
|
h ^ 1
|
|
}
|
|
|
|
/// The segments of half-edge `h`, in its direction.
|
|
pub(super) fn half_segs(edges: &[Edge], h: usize) -> Vec<Seg> {
|
|
half_seg_iter(edges, h).collect()
|
|
}
|
|
|
|
/// The segments of half-edge `h` in its direction, without copying the edge first (an edge can
|
|
/// hold thousands of segments, and the angle it leaves a vertex at needs the first few).
|
|
fn half_seg_iter(edges: &[Edge], h: usize) -> impl Iterator<Item = Seg> + '_ {
|
|
let segs = edges.get(h / 2).map(|e| e.segs.as_slice()).unwrap_or_default();
|
|
let forward = h.is_multiple_of(2);
|
|
(0..segs.len()).filter_map(move |k| {
|
|
let s = segs.get(if forward { k } else { segs.len() - 1 - k })?;
|
|
Some(if forward { *s } else { Seg { c: CubicBez::new(s.c.p3, s.c.p2, s.c.p1, s.c.p0), line: s.line } })
|
|
})
|
|
}
|
|
|
|
/// Where half-edge `h` ends (the vertex at the other end of its edge).
|
|
pub(super) fn far_end(edges: &[Edge], h: usize) -> Option<Point> {
|
|
let e = edges.get(h / 2)?;
|
|
if h.is_multiple_of(2) { e.segs.last().map(|s| s.c.p3) } else { e.segs.first().map(|s| s.c.p0) }
|
|
}
|
|
|
|
/// The vertex half-edge `h` leaves from.
|
|
pub(super) fn origin(edges: &[Edge], h: usize) -> Option<usize> {
|
|
edges.get(h / 2).map(|e| e.ends[h % 2])
|
|
}
|
|
|
|
/// Where half-edge `h` (leaving `v`) first gets `r` away from it, as an angle around `v`.
|
|
///
|
|
/// Edges that only meet at `v` cross a circle around it in the order they leave `v` in, however
|
|
/// they start out (tangent curves, or the sweep's short straight stubs at a vertex).
|
|
pub(super) fn leaving_angle(edges: &[Edge], h: usize, v: Point, r: f64) -> f64 {
|
|
let first = half_seg_iter(edges, h).next();
|
|
let mut prev = first.map_or(v, |s| s.c.p0);
|
|
let mut hit: Option<Point> = None;
|
|
// One segment at a time, stopping at the first that gets r away: an edge can run on for
|
|
// thousands of segments (a traced outline between two crossings), all of which flattening
|
|
// the whole edge to this tolerance would visit, at every vertex.
|
|
for s in half_seg_iter(edges, h) {
|
|
let path = [kurbo::PathEl::MoveTo(s.c.p0), kurbo::PathEl::CurveTo(s.c.p1, s.c.p2, s.c.p3)];
|
|
kurbo::flatten(path, (r * 1e-4).max(1e-12), |el| {
|
|
if let (None, kurbo::PathEl::LineTo(p)) = (hit, el) {
|
|
if p.distance(v) >= r {
|
|
// The point on prev → p at distance r (prev is closer).
|
|
let (d, f) = (p - prev, prev - v);
|
|
let (a, b, c) = (d.hypot2(), 2.0 * f.dot(d), f.hypot2() - r * r);
|
|
let t = if a > 0.0 { ((-b + (b * b - 4.0 * a * c).max(0.0).sqrt()) / (2.0 * a)).clamp(0.0, 1.0) } else { 1.0 };
|
|
hit = Some(prev + d * t);
|
|
}
|
|
prev = p;
|
|
}
|
|
});
|
|
if hit.is_some() {
|
|
break;
|
|
}
|
|
}
|
|
// An edge too short to reach r (it can't, r is at most half of every chord) leaves along its
|
|
// tangent.
|
|
let far = hit.unwrap_or_else(|| first.map_or(v, |s| [s.c.p1, s.c.p2, s.c.p3].into_iter().find(|p| p.distance(s.c.p0) > 0.0).unwrap_or(s.c.p3)));
|
|
(far - v).atan2()
|
|
}
|
|
|
|
/// Path of a closed run of half-edges, tidied (split pieces of one input curve joined again).
|
|
pub(super) fn cycle_bezpath(edges: &[Edge], cycle: &[usize]) -> BezPath {
|
|
let mut bp = BezPath::new();
|
|
for (k, s) in cycle.iter().flat_map(|&h| half_segs(edges, h)).enumerate() {
|
|
if k == 0 {
|
|
bp.move_to(s.c.p0);
|
|
}
|
|
if s.line {
|
|
bp.line_to(s.c.p3);
|
|
} else {
|
|
bp.curve_to(s.c.p1, s.c.p2, s.c.p3);
|
|
}
|
|
}
|
|
bp.close_path();
|
|
bp
|
|
}
|
|
|
|
/// `cycle` without the spurs it runs out and back along (edges with the same face on both sides,
|
|
/// such as a line's loose end).
|
|
pub(super) fn without_spurs(cycle: &[usize]) -> Vec<usize> {
|
|
let mut out: Vec<usize> = Vec::with_capacity(cycle.len());
|
|
for &h in cycle {
|
|
if out.last() == Some(&twin(h)) {
|
|
out.pop();
|
|
} else {
|
|
out.push(h);
|
|
}
|
|
}
|
|
// A spur where the walk started wraps around the ends.
|
|
while out.len() >= 2 && out.first().map(|&h| twin(h)) == out.last().copied() {
|
|
out.pop();
|
|
out.remove(0);
|
|
}
|
|
out
|
|
}
|
|
|
|
/// Append `open` traced there and back (a closed, zero-area path along it) to `out`.
|
|
pub(super) fn there_and_back(open: &BezPath, out: &mut BezPath) {
|
|
let segs: Vec<PathSeg> = open.segments().collect();
|
|
let Some(first) = segs.first() else { return };
|
|
out.move_to(first.start());
|
|
for s in &segs {
|
|
out.push(s.as_path_el());
|
|
}
|
|
for s in segs.iter().rev() {
|
|
out.push(s.reverse().as_path_el());
|
|
}
|
|
out.close_path();
|
|
}
|
|
|
|
/// `path` back from the sweep's frame, its anchors within `tol` of an input anchor (`sorted` by x)
|
|
/// put exactly on it, handles moving with them.
|
|
pub(super) fn unshear(mut path: PathData, sorted: &[Point], tol: f64) -> PathData {
|
|
path.transform(UNSHEAR);
|
|
for a in path.subpaths.iter_mut().flat_map(|sp| sp.anchors.iter_mut()) {
|
|
let lo = sorted.partition_point(|q| q.x < a.p.x - tol);
|
|
if let Some(q) = sorted.get(lo..).unwrap_or_default().iter().take_while(|q| q.x <= a.p.x + tol).find(|q| (q.y - a.p.y).abs() <= tol) {
|
|
let d = *q - a.p;
|
|
a.p = *q;
|
|
a.h_in += d;
|
|
a.h_out += d;
|
|
}
|
|
}
|
|
path
|
|
}
|
|
|
|
pub(super) fn find(parent: &mut [usize], mut v: usize) -> usize {
|
|
while let Some(&p) = parent.get(v) {
|
|
if p == v {
|
|
break;
|
|
}
|
|
let gp = parent.get(p).copied().unwrap_or(p);
|
|
if let Some(slot) = parent.get_mut(v) {
|
|
*slot = gp;
|
|
}
|
|
v = p;
|
|
}
|
|
v
|
|
}
|