Thanks @SychPL.
This commit is contained in:
1 parent
3eae8c34f6
commit
a2245b2f51
2 files changed
+125
-18
No files matched your search
@@ -639,7 +639,9 @@ fn find_profiles_bounded(sk: &Sketch, max_edges: usize) -> Vec<Profile> {
|
||||
_ => false,
|
||||
};
|
||||
let grid = Grid::new(&boxes);
|
||||
let mut cuts: Vec<Vec<f64>> = vec![Vec::new(); shapes.len()];
|
||||
// Each cut's parameter, and whether it is weak: a crossing next to an end that misses one of
|
||||
// the two curves (below), which gives way to any other cut it is merged with.
|
||||
let mut cuts: Vec<Vec<(f64, bool)>> = vec![Vec::new(); shapes.len()];
|
||||
// End points of other curves lying on each shape.
|
||||
let mut end_cells: std::collections::HashMap<(i64, i64), Vec<usize>> = std::collections::HashMap::new();
|
||||
for (k, p) in ends.iter().enumerate() {
|
||||
@@ -657,7 +659,7 @@ fn find_profiles_bounded(sk: &Sketch, max_edges: usize) -> Vec<Profile> {
|
||||
&& let Some(t) = sh.param_on(*p, tol)
|
||||
&& let Some(cu) = cuts.get_mut(i)
|
||||
{
|
||||
cu.push(t);
|
||||
cu.push((t, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -752,30 +754,35 @@ fn find_profiles_bounded(sk: &Sketch, max_edges: usize) -> Vec<Profile> {
|
||||
for p in pts {
|
||||
let on_x = x.param_on(p, tol).or_else(|| end_param(x, p, tol));
|
||||
let on_y = y.param_on(p, tol).or_else(|| end_param(y, p, tol));
|
||||
// Crossings at (or next to) curve ends are already cuts.
|
||||
let near_end = || {
|
||||
// Crossings at (or next to) curve ends are already cuts. Not next to an end of
|
||||
// one of the two curves that misses the other: nothing cut the other curve
|
||||
// there, and it would cross without meeting it (a line 3e-5 from the
|
||||
// junction of two curves of a loop, #755). Such a crossing is a weak cut.
|
||||
let on = |sh: &Shape, e: Vec2| sh.param_on(e, tol).is_some() || end_param(sh, e, tol).is_some();
|
||||
let misses = |e: Vec2| (end_param(x, e, tol).is_some() && !on(y, e)) || (end_param(y, e, tol).is_some() && !on(x, e));
|
||||
let near_ends = || {
|
||||
let (cx, cy) = grid.cell(p);
|
||||
(-1..=1).any(|dx| {
|
||||
(-1..=1).any(|dy| {
|
||||
end_cells
|
||||
.get(&(cx + dx, cy + dy))
|
||||
.is_some_and(|v| v.iter().any(|k| ends.get(*k).is_some_and(|e| e.dist(p) < tol * 100.0)))
|
||||
})
|
||||
})
|
||||
(-1..=1)
|
||||
.flat_map(|dx| (-1..=1).map(move |dy| (cx + dx, cy + dy)))
|
||||
.flat_map(|c| end_cells.get(&c).map(Vec::as_slice).unwrap_or(&[]))
|
||||
.filter_map(|k| ends.get(*k).copied())
|
||||
.filter(|e| e.dist(p) < tol * 100.0)
|
||||
.fold((false, false), |(cut, weak), e| if misses(e) { (cut, true) } else { (true, weak) })
|
||||
};
|
||||
if on_x.is_none() || on_y.is_none() || near_end() {
|
||||
let (already, weak) = near_ends();
|
||||
if on_x.is_none() || on_y.is_none() || already {
|
||||
continue;
|
||||
}
|
||||
if let (Some(tx), Some(ty)) = (on_x, on_y) {
|
||||
if x.param_on(p, tol).is_some()
|
||||
&& let Some(c) = cuts.get_mut(i)
|
||||
{
|
||||
c.push(tx);
|
||||
c.push((tx, weak));
|
||||
}
|
||||
if y.param_on(p, tol).is_some()
|
||||
&& let Some(c) = cuts.get_mut(j)
|
||||
{
|
||||
c.push(ty);
|
||||
c.push((ty, weak));
|
||||
}
|
||||
if (gi, gj) != (i, j) {
|
||||
anchors.push(p);
|
||||
@@ -815,21 +822,33 @@ fn find_profiles_bounded(sk: &Sketch, max_edges: usize) -> Vec<Profile> {
|
||||
let mut edges: Vec<Edge> = Vec::new();
|
||||
for (k, (ci, sh)) in shapes.iter().enumerate() {
|
||||
let mut ts = cuts.get(k).cloned().unwrap_or_default();
|
||||
ts.sort_by(f64::total_cmp);
|
||||
ts.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
// Cuts closer than a few tolerances are one cut (tangent touches give near-double roots).
|
||||
// A weak cut gives way to the other: it only adds a cut where there was none.
|
||||
let scale = match *sh {
|
||||
Shape::Line { a, b } => a.dist(b),
|
||||
Shape::Round { r, .. } => r,
|
||||
};
|
||||
let close = (tol * 100.0 / scale.max(1e-12)).max(1e-9);
|
||||
ts.dedup_by(|a, b| (*a - *b).abs() < close);
|
||||
ts.dedup_by(|a, b| {
|
||||
let same = (a.0 - b.0).abs() < close;
|
||||
if same && b.1 && !a.1 {
|
||||
*b = *a;
|
||||
}
|
||||
same
|
||||
});
|
||||
if let (Shape::Round { sweep, .. }, Some(&first), Some(&last)) = (*sh, ts.first(), ts.last())
|
||||
&& sweep >= TAU - 1e-12
|
||||
&& ts.len() > 1
|
||||
&& (first + TAU - last).abs() < close
|
||||
&& (first.0 + TAU - last.0).abs() < close
|
||||
{
|
||||
ts.pop();
|
||||
if first.1 && !last.1 {
|
||||
ts.remove(0);
|
||||
} else {
|
||||
ts.pop();
|
||||
}
|
||||
}
|
||||
let ts: Vec<f64> = ts.into_iter().map(|(t, _)| t).collect();
|
||||
let id = sk.curves.get(*ci).map(|c| c.id.clone()).unwrap_or_default();
|
||||
let analytic = analytic(*ci);
|
||||
let coverage = tags.get(k).copied().flatten().or_else(|| {
|
||||
|
||||
@@ -967,3 +967,91 @@ fn a_degree_nine_control_spline_stays_exact_and_closes_a_profile_exactly() {
|
||||
high.push(sk.add_point(v(21.0, -2.0), None).unwrap());
|
||||
assert!(sk.add_curve(CurveKind::Spline { pts: high, control: true, degree: 10, periodic: false }, None).is_err());
|
||||
}
|
||||
|
||||
/// #755: a line drawn through a junction of a loop, but 2e-5 off it, still splits the loop. The
|
||||
/// junction lies on the loop's two curves only, so it cuts neither of them where the line
|
||||
/// crosses, and that crossing, next to the junction, was taken for one already cut there: the
|
||||
/// line crossed the loop without meeting it, and the sketch had no profiles at all.
|
||||
#[test]
|
||||
fn profiles_line_passing_next_to_a_junction_splits_the_loop() {
|
||||
let o = v(0.0, 0.0);
|
||||
let at = |r: f64, a: f64| v(r * a.cos(), r * a.sin());
|
||||
// A slot: half circles of radius 1 round (0, 0) and (-5, 0), joined by the lines y = 1 and
|
||||
// y = -1, which meet the arcs tangentially at (0, 1) and (0, -1).
|
||||
let mut sk = Sketch::new();
|
||||
let right = sk.add_arc(o, v(0.0, -1.0), v(0.0, 1.0), [None; 3], None).unwrap();
|
||||
let left = sk.add_arc(v(-5.0, 0.0), v(-5.0, 1.0), v(-5.0, -1.0), [None; 3], None).unwrap();
|
||||
let ends = |i: usize| match sk.curves[i].kind {
|
||||
CurveKind::Arc { a, b, .. } => (a, b),
|
||||
_ => panic!("an arc"),
|
||||
};
|
||||
let ((r0, r1), (l0, l1)) = (ends(right), ends(left));
|
||||
sk.add_line_pts(r1, l0, None).unwrap();
|
||||
sk.add_line_pts(l1, r0, None).unwrap();
|
||||
// Lines from (0, 0) through the junctions to an arc of radius 2. The upper one is turned by
|
||||
// 2e-5 rad, so it passes 2e-5 from (0, 1) and crosses the line y = 1 next to it.
|
||||
let d = 2e-5;
|
||||
let (top, bottom) = (at(2.0, FRAC_PI_2 + d), v(0.0, -2.0));
|
||||
sk.add_line(o, top, None, None, None).unwrap();
|
||||
sk.add_line(o, bottom, None, None, None).unwrap();
|
||||
sk.add_arc(o, bottom, top, [None; 3], None).unwrap();
|
||||
let mut areas: Vec<f64> = find_profiles(&sk).iter().map(|p| p.area).collect();
|
||||
areas.sort_by(f64::total_cmp);
|
||||
// The right half disc, the band round it out to radius 2, and the rest of the slot.
|
||||
let want = [FRAC_PI_2, FRAC_PI_2 * (4.0 - 1.0), 10.0 + FRAC_PI_2];
|
||||
assert_eq!(areas.len(), 3, "{areas:?}");
|
||||
for (got, want) in areas.iter().zip(want) {
|
||||
assert!((got - want).abs() < 1e-3, "{areas:?} against {want}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A line ending 2e-5 from a corner, next to a crossing there: the corner, where the side of the
|
||||
/// square meets the long line, stays a cut of the long line, and the square stays a profile.
|
||||
#[test]
|
||||
fn profiles_crossing_next_to_a_corner_keeps_the_corner() {
|
||||
let mut sk = Sketch::new();
|
||||
let (a, b) = (sk.add_point(v(1.0, 0.0), None).unwrap(), sk.add_point(v(2.0, 0.0), None).unwrap());
|
||||
let (c, d) = (sk.add_point(v(2.0, 1.0), None).unwrap(), sk.add_point(v(1.0, 1.0), None).unwrap());
|
||||
// The square's base is part of a longer line, which its sides end on.
|
||||
sk.add_line(v(0.0, 0.0), v(3.0, 0.0), None, None, None).unwrap();
|
||||
for (p, q) in [(b, c), (c, d), (d, a)] {
|
||||
sk.add_line_pts(p, q, None).unwrap();
|
||||
}
|
||||
// A line hanging across the long one, 2e-5 from the square's corner (1, 0).
|
||||
sk.add_line(v(1.0 - 2e-5, -0.5), v(1.0 - 2e-5, 0.5), None, None, None).unwrap();
|
||||
let areas: Vec<f64> = find_profiles(&sk).iter().map(|p| p.area).collect();
|
||||
assert_eq!(areas.len(), 1, "{areas:?}");
|
||||
assert!(areas.iter().all(|x| (x - 1.0).abs() < 1e-9), "{areas:?}");
|
||||
}
|
||||
|
||||
/// A line across a rectangle, and a stray line ending on it 1e-3 from where it crosses the
|
||||
/// rectangle's side: both the crossing and that end cut the long line, and the rectangle is
|
||||
/// split into its two halves.
|
||||
#[test]
|
||||
fn profiles_crossing_next_to_a_stray_end_still_splits() {
|
||||
let mut sk = Sketch::new();
|
||||
rect(&mut sk, [v(0.0, -1.0), v(1.0, -1.0), v(1.0, 1.0), v(0.0, 1.0)]);
|
||||
sk.add_line(v(-1.0, 0.0), v(2.0, 0.0), None, None, None).unwrap();
|
||||
sk.add_line(v(0.00099998, 0.0000099), v(0.3, 0.6), None, None, None).unwrap();
|
||||
let areas: Vec<f64> = find_profiles(&sk).iter().map(|p| p.area).collect();
|
||||
assert_eq!(areas.len(), 2, "{areas:?}");
|
||||
assert!(areas.iter().all(|x| (x - 1.0).abs() < 1e-9), "{areas:?}");
|
||||
}
|
||||
|
||||
/// A line whose own end overshoots a crossing line by 2e-5, with a rectangle standing on it 1e-3
|
||||
/// further on: the crossing next to that end gives way to the rectangle's corner, which still
|
||||
/// cuts the line, and the rectangle stays a profile.
|
||||
#[test]
|
||||
fn profiles_crossing_next_to_an_overshooting_end_keeps_the_corner_beside_it() {
|
||||
let mut sk = Sketch::new();
|
||||
sk.add_line(v(-2e-5, 0.0), v(3.0, 0.0), None, None, None).unwrap();
|
||||
sk.add_line(v(0.0, -0.5), v(0.0, 0.5), None, None, None).unwrap();
|
||||
let x = 0.00099999;
|
||||
let p = [v(x, 0.0000099), v(x, 1.0), v(2.0, 1.0), v(2.0, 0.0)].map(|q| sk.add_point(q, None).unwrap());
|
||||
for w in p.windows(2) {
|
||||
sk.add_line_pts(w[0], w[1], None).unwrap();
|
||||
}
|
||||
let areas: Vec<f64> = find_profiles(&sk).iter().map(|p| p.area).collect();
|
||||
assert_eq!(areas.len(), 1, "{areas:?}");
|
||||
assert!(areas.iter().all(|a| (a - (2.0 - x)).abs() < 1e-4), "{areas:?}");
|
||||
}
|
||||
Reference in new issue
Block a user