fix: a planar union leaves no sliver where an edge crosses its neighbour by their shared corner (#1058)
A planar union no longer leaves a sliver where an edge crosses its neighbour, so a sheet whose outline runs on past a flange's edge with a slight bend unfolds again.
This commit is contained in:
1 parent
7879875d49
commit
435ad0fd8c
2 files changed
+94
-2
No files matched your search
@@ -1127,6 +1127,13 @@ fn boolean2d_tagged(a: &Section, b: &Section, op: BoolOp, tol: f64) -> Option<Se
|
||||
}
|
||||
}
|
||||
}
|
||||
// The loops below are chained by joining pieces whose ends lie within `near` of each other,
|
||||
// so to them a piece shorter than that runs from a point back to the same point: a cut that
|
||||
// would leave one, at either end of what is left of the segment, is not made. Two edges
|
||||
// 0.57° apart that share a corner up to rounding cross a hundred times that rounding short
|
||||
// of it; split there, the sliver between the crossing and the corner was taken for a
|
||||
// second way out of the corner and closed as a loop of its own.
|
||||
let near = tol * 10.0;
|
||||
let pieces = |s: &Seg2, cuts: &mut Vec<f64>| -> Vec<Seg2> {
|
||||
cuts.sort_by(f64::total_cmp);
|
||||
let mut out = Vec::new();
|
||||
@@ -1138,7 +1145,7 @@ fn boolean2d_tagged(a: &Section, b: &Section, op: BoolOp, tol: f64) -> Option<Se
|
||||
}
|
||||
let local = (c - done) / (1.0 - done);
|
||||
let (x, y) = rest.split_at(local);
|
||||
if x.length() > tol {
|
||||
if x.length() > near && y.length() > near {
|
||||
out.push(x);
|
||||
rest = y;
|
||||
done = c;
|
||||
@@ -1210,7 +1217,6 @@ fn boolean2d_tagged(a: &Section, b: &Section, op: BoolOp, tol: f64) -> Option<Se
|
||||
// material meeting only at that point close as loops of their own; returning to its start,
|
||||
// a loop closes only when that is the tightest turn too. A loop that still passes a point
|
||||
// twice (a hole touching the outline) is split there into simple loops.
|
||||
let near = tol * 10.0;
|
||||
let mut loops: Vec<(Loop2, Vec<Tag>)> = Vec::new();
|
||||
let mut used = vec![false; kept.len()];
|
||||
for start in 0..kept.len() {
|
||||
@@ -2746,6 +2752,56 @@ mod planar_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A 55 x 30 plate whose bottom edge bends up by `slope` over its last 5 mm before the
|
||||
/// corner at (100, 50), and a strip 3 wide below the straight 50 mm, as a sheet's bend zone
|
||||
/// lies against its base: the strip's corners on that edge rounded to the nanometre, as a
|
||||
/// flange keeps them, and moved by `off`. Both turned by `theta` about the origin.
|
||||
fn plate_and_strip_by_a_bent_edge(theta: f64, slope: f64, off: Vec2) -> (Region2, Region2) {
|
||||
let (sn, cs) = theta.sin_cos();
|
||||
let rot = |x: f64, y: f64| Vec2::new(x * cs - y * sn, x * sn + y * cs);
|
||||
let nm = |p: Vec2| Vec2::new((p.x * 1e9).round() / 1e9, (p.y * 1e9).round() / 1e9) + off;
|
||||
let plate = [rot(95.0, 50.0 + 5.0 * slope), rot(100.0, 50.0), rot(150.0, 50.0), rot(150.0, 80.0), rot(95.0, 80.0)];
|
||||
let strip = [nm(rot(100.0, 50.0)), rot(100.0, 47.0), rot(150.0, 47.0), nm(rot(150.0, 50.0))];
|
||||
(Region2 { outer: Loop2::polygon(&plate), holes: vec![] }, Region2 { outer: Loop2::polygon(&strip), holes: vec![] })
|
||||
}
|
||||
|
||||
/// The bent piece of the plate's edge, a hair from the strip's edge, crosses it short of
|
||||
/// the corner they share: the corners a nanometre apart at most, the crossing up to a
|
||||
/// hundred times that before it at a slope of 1 in 100, a thousand at 1 in 1000. A split
|
||||
/// there left a sliver of edge shorter than the distance within which the loops join their
|
||||
/// pieces; the chaining took it for a second way out of the corner, and a loop of that one
|
||||
/// sliver is open, its area not zero away from the origin: a hole no outline held (no
|
||||
/// result at all) or a region of its own. The union is the plate and the strip, whole.
|
||||
#[test]
|
||||
fn a_bent_edge_crossing_its_neighbour_by_their_corner_leaves_no_sliver() {
|
||||
let union_is_whole = |(plate, strip): (Region2, Region2), what: &str| {
|
||||
let want = plate.area() + strip.area();
|
||||
for (a, b) in [(&plate, &strip), (&strip, &plate)] {
|
||||
let out = planar_regions_boolean(std::slice::from_ref(a), std::slice::from_ref(b), BoolOp::Union, 1e-7)
|
||||
.unwrap_or_else(|e| panic!("{what}: {e}"));
|
||||
assert_eq!(out.len(), 1, "{what}: {out:?}");
|
||||
let lp = &out[0].outer;
|
||||
let shortest = lp.segs.iter().map(Seg2::length).fold(f64::MAX, f64::min);
|
||||
assert!(out[0].holes.is_empty() && lp.segs.len() == 7 && shortest > 2.9, "{what}: {} edges, shortest {shortest:e}", lp.segs.len());
|
||||
assert!((out[0].area() - want).abs() < 1e-6, "{what}: {} vs {want}", out[0].area());
|
||||
}
|
||||
};
|
||||
for k in 0..360 {
|
||||
for slope in [0.01, 0.001] {
|
||||
union_is_whole(plate_and_strip_by_a_bent_edge((k as f64 * 0.25).to_radians(), slope, Vec2::ZERO), &format!("{k} slope {slope}"));
|
||||
}
|
||||
}
|
||||
// Corners a few nanometres apart, any way round: the sliver is then longer than the
|
||||
// tolerance (1e-7 with the strip 1e-9 into the plate) but shorter than the joining
|
||||
// distance (1e-6).
|
||||
for k in 0..16 {
|
||||
for d in [1e-9, 4e-9] {
|
||||
let off = Vec2::from_angle(k as f64 * std::f64::consts::TAU / 16.0) * d;
|
||||
union_is_whole(plate_and_strip_by_a_bent_edge(0.0, 0.01, off), &format!("off {off:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Touching circles meet at one point on the line of their centres, inside and outside.
|
||||
#[test]
|
||||
fn touching_lines_and_arcs_meet_at_one_point() {
|
||||
|
||||
Reference in new issue
Block a user