use super::*;
const EPS: f64 = 1e-6;
pub(super) type Clipped = (Vec<Subpath>, Vec<f64>, Vec<f64>);
pub(super) fn clip_out(
subs: Vec<Subpath>,
vertical: bool,
a: f64,
b: f64,
span: Span,
) -> Result<Clipped, Error> {
let t = |p: P| if vertical { p.1 } else { p.0 };
let cross = |p: P| if vertical { p.0 } else { p.1 };
let mut out = Vec::with_capacity(subs.len());
let (mut xa, mut xb) = (Vec::new(), Vec::new());
for sub in subs {
let mut pieces: Vec<(PathSeg, bool)> = Vec::with_capacity(sub.segs.len());
let mut any_dropped = false;
for seg in &sub.segs {
if let PathSeg::Cubic { from, c1, c2, to } = seg {
let ts = [t(*from), t(*c1), t(*c2), t(*to)];
let (lo, hi) = ts
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |acc, v| {
(acc.0.min(*v), acc.1.max(*v))
});
for station in [a, b] {
if lo < station - EPS && hi > station + EPS {
return Err(Error::at(
span,
"a 'break' can't cut a 'curve()' — move the stations",
));
}
}
}
for piece in split_seg(*seg, vertical, a, b) {
let m = t(piece_mid(&piece));
let keep = m <= a + EPS || m >= b - EPS;
any_dropped |= !keep;
pieces.push((piece, keep));
}
}
if !any_dropped {
out.push(sub);
continue;
}
let n = pieces.len();
let start = pieces
.iter()
.position(|(_, keep)| !keep)
.expect("something dropped");
let order: Vec<usize> = if sub.closed {
(1..=n).map(|i| (start + i) % n).collect()
} else {
(0..n).collect()
};
let mut run: Vec<PathSeg> = Vec::new();
let mut flush = |run: &mut Vec<PathSeg>| {
if run.is_empty() {
return;
}
for p in [
run.first().expect("non-empty").from(),
run.last().expect("non-empty").to(),
] {
if (t(p) - a).abs() < EPS {
xa.push(cross(p));
} else if (t(p) - b).abs() < EPS {
xb.push(cross(p));
}
}
out.push(Subpath {
segs: std::mem::take(run),
closed: false,
});
};
for i in order {
let (piece, keep) = &pieces[i];
if *keep {
if let Some(last) = run.last()
&& dist(last.to(), piece.from()) > EPS
{
flush(&mut run);
}
run.push(*piece);
} else {
flush(&mut run);
}
}
flush(&mut run);
}
Ok((out, xa, xb))
}
fn piece_mid(seg: &PathSeg) -> P {
match *seg {
PathSeg::Line { from, to } => ((from.0 + to.0) / 2.0, (from.1 + to.1) / 2.0),
PathSeg::Arc {
from,
to,
r,
large,
sweep,
} => {
let c = arc_center(from, to, r, large, sweep);
let (a0, travel) = arc_travel(from, to, c, sweep);
arc_point(c, r, a0 + travel / 2.0 * if sweep { 1.0 } else { -1.0 })
}
PathSeg::Cubic { from, c1, c2, to } => (
(from.0 + 3.0 * c1.0 + 3.0 * c2.0 + to.0) / 8.0,
(from.1 + 3.0 * c1.1 + 3.0 * c2.1 + to.1) / 8.0,
),
}
}
fn split_seg(seg: PathSeg, vertical: bool, a: f64, b: f64) -> Vec<PathSeg> {
let t = |p: P| if vertical { p.1 } else { p.0 };
match seg {
PathSeg::Line { from, to } => {
let (t0, t1) = (t(from), t(to));
let mut cuts: Vec<f64> = [a, b]
.into_iter()
.filter_map(|c| {
let s = (c - t0) / (t1 - t0);
(s.is_finite() && s > EPS && s < 1.0 - EPS).then_some(s)
})
.collect();
cuts.sort_by(f64::total_cmp);
let at = |s: f64| (from.0 + (to.0 - from.0) * s, from.1 + (to.1 - from.1) * s);
let mut pts = vec![from];
pts.extend(cuts.into_iter().map(at));
pts.push(to);
pts.windows(2)
.map(|w| PathSeg::Line {
from: w[0],
to: w[1],
})
.collect()
}
PathSeg::Arc {
from,
to,
r,
large,
sweep,
} => {
let c = arc_center(from, to, r, large, sweep);
let (a0, travel) = arc_travel(from, to, c, sweep);
let dir = if sweep { 1.0 } else { -1.0 };
let mut hits: Vec<f64> = Vec::new();
for station in [a, b] {
let d = station - if vertical { c.1 } else { c.0 };
if d.abs() >= r - EPS {
continue;
}
let h = (r * r - d * d).sqrt();
for other in [h, -h] {
let p = if vertical {
(c.0 + other, station)
} else {
(station, c.1 + other)
};
let aq = (p.1 - c.1).atan2(p.0 - c.0);
let along = ((aq - a0) * dir).rem_euclid(std::f64::consts::TAU);
if along > EPS && along < travel - EPS {
hits.push(along);
}
}
}
hits.sort_by(f64::total_cmp);
hits.dedup_by(|p, q| (*p - *q).abs() < EPS);
let mut angles = vec![0.0];
angles.extend(hits);
angles.push(travel);
angles
.windows(2)
.map(|w| {
let (p, q) = (
arc_point(c, r, a0 + w[0] * dir),
arc_point(c, r, a0 + w[1] * dir),
);
PathSeg::Arc {
from: if w[0] == 0.0 { from } else { p },
to: if w[1] == travel { to } else { q },
r,
large: w[1] - w[0] > std::f64::consts::PI,
sweep,
}
})
.collect()
}
PathSeg::Cubic { .. } => vec![seg],
}
}
fn arc_travel(from: P, to: P, c: P, sweep: bool) -> (f64, f64) {
let a0 = (from.1 - c.1).atan2(from.0 - c.0);
let a1 = (to.1 - c.1).atan2(to.0 - c.0);
let dir = if sweep { 1.0 } else { -1.0 };
(a0, ((a1 - a0) * dir).rem_euclid(std::f64::consts::TAU))
}
fn arc_point(c: P, r: f64, angle: f64) -> P {
(c.0 + r * angle.cos(), c.1 + r * angle.sin())
}