Envelopes: document how a straight segment's handles count in the top object corner test

A top object's corner test treats a straight segment as having its
handles a third of the way along it, so an anchor between two straight
segments is a corner only when it turns out of the outline. docs/mcp.md
and the doc comment now say so, and a new test covers an inward anchor
between straight segments. The bulging-curve corner test checks against
a fixed point instead of the same nearest-point routine the code uses.
This commit is contained in:
dotCooCoo
2026-11-09 00:31:07 +07:00
committed by bflatastic
parent ed6c0d434f
commit 51bab66409
3 changed files with 21 additions and 7 deletions
+14 -5
View File
@@ -262,18 +262,17 @@ mod tests {
fn a_top_object_corner_inside_a_bulging_curve_is_the_nearest_outline_point() {
// The top left anchor's handle bulges out past the corner, so the anchor turns inward
// there and the corner falls inside the curve.
let top = CubicBez::new((0.0, 0.0), (-50.0, -50.0), (150.0, 0.0), (200.0, 0.0));
// The outline's bounding box starts at (-7.957, -22.222); the top curve's point nearest
// that corner, found by sampling the curve 2,000,000 times, is (-5.072093, -18.127254).
let path = outline(&[
((0.0, 0.0), (0.0, 0.0), (-50.0, -50.0)),
((200.0, 0.0), (150.0, 0.0), (200.0, 0.0)),
((200.0, 100.0), (200.0, 100.0), (200.0, 100.0)),
((0.0, 100.0), (0.0, 100.0), (0.0, 100.0)),
]);
use vectorcraft_geom::kurbo::ParamCurveNearest;
let b = path.subpaths[0].segment(3).bounding_box().union(top.bounding_box());
let want = top.eval(top.nearest(Point::new(b.x0, b.y0), 1e-12).t);
let want = Point::new(-5.072093, -18.127254);
let tl = Coons::from_top_object(&path, None).unwrap().corners()[0];
assert!(tl.distance(want) < 1e-6, "{tl:?}, want {want:?}");
assert!(tl.distance(want) < 1e-5, "{tl:?}, want {want:?}");
assert!(tl.distance(Point::ZERO) > 1.0, "{tl:?}");
}
@@ -316,6 +315,16 @@ mod tests {
}
}
#[test]
fn a_top_object_corner_skips_an_inward_anchor_between_straight_segments() {
// A 200 × 100 outline of straight segments with a notch at its top left: (40,40) is the
// anchor nearest the box corner, but it turns into the outline, so (60,0) is the corner.
let c = |p: (f64, f64)| (p, p, p);
let path = outline(&[c((60.0, 0.0)), c((200.0, 0.0)), c((200.0, 100.0)), c((0.0, 100.0)), c((0.0, 60.0)), c((40.0, 40.0))]);
let tl = Coons::from_top_object(&path, None).unwrap().corners()[0];
assert!(tl.distance(Point::new(60.0, 0.0)) < 1e-9, "{tl:?}");
}
#[test]
fn a_top_object_corner_tie_goes_to_the_first_anchor() {
// Both chamfer anchors lie 30 from the box corner: the first one along the outline, in
+4 -1
View File
@@ -354,7 +354,10 @@ const CORNER_END_SNAP: f64 = 0.005;
/// Is the anchor at `a`, reached by handle `h_in` and left by handle `h_out` on an outline that
/// runs clockwise on screen, a corner of a top object? A handle of [`SHORT_HANDLE`] or shorter
/// makes it one. Otherwise its tangent directions must differ by [`SMOOTH_TURN`] or more and turn
/// clockwise, so it points out of the outline.
/// clockwise, so it points out of the outline. A straight segment's handles here are the ones
/// [`Coons::from_top_object`] gives it, a third of the way along, so an anchor between two
/// straight segments longer than three times [`SHORT_HANDLE`] is a corner only when it turns out
/// of the outline.
fn top_object_corner_anchor(h_in: Point, a: Point, h_out: Point) -> bool {
let (vin, vout) = (a - h_in, h_out - a);
let (len_in, len_out) = (vin.hypot(), vout.hypot());