From d91440fd7aa9974e2efcf83ff3a079f92f0b0e65 Mon Sep 17 00:00:00 2001 From: Johannes Wolf Date: Wed, 21 Aug 2024 01:00:34 +0200 Subject: [PATCH] Find closest point by name --- CHANGES.md | 2 +- src/bezier.typ | 10 ++++++++-- src/draw.typ | 2 +- src/draw/grouping.typ | 26 +++++++++++++++--------- tests/closest-point/ref/1.png | Bin 0 -> 10598 bytes tests/closest-point/test.typ | 36 ++++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 tests/closest-point/ref/1.png create mode 100644 tests/closest-point/test.typ diff --git a/CHANGES.md b/CHANGES.md index 7d14c0b59..6b4eaeb9a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,7 +29,7 @@ package called `cetz-plot`. depth ordering and face culling of drawables. Ordering is enabled by default. - Closed `line` and `merge-path` elements now have a `"centroid"` anchor that is the calculated centroid of the (non self-intersecting!) shape. -- Added `closest-point` for creating an anchor at the closest point between a +- Added `find-closest-point` for creating an anchor at the closest point between a reference point and one or more elements. ## Marks diff --git a/src/bezier.typ b/src/bezier.typ index 6a17e737d..852370778 100644 --- a/src/bezier.typ +++ b/src/bezier.typ @@ -586,8 +586,14 @@ return pts } -/// Find the closest point on a bezier to a given point -/// by using a binary search along the curve. +/// Find the closest point on a bezier to a given point. +/// +/// - pt (vector): Reference point to find the closest point to +/// - s (vector): Bezier start +/// - e (vector): Bezier end +/// - c1 (vector): Bezier control point 1 +/// - c2 (vector): Bezier control point 2 +/// - max-recursion (int): Max recursion depth #let cubic-closest-point(pt, s, e, c1, c2, max-recursion: 1) = { let probe(low, high, depth) = { let min = calc.inf diff --git a/src/draw.typ b/src/draw.typ index 6b36631c0..5c3bff4d1 100644 --- a/src/draw.typ +++ b/src/draw.typ @@ -1,4 +1,4 @@ -#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks, hide, floating, closest-point +#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks, hide, floating, find-closest-point #import "draw/transformations.typ": set-transform, rotate, translate, scale, set-origin, move-to, set-viewport #import "draw/styling.typ": set-style, fill, stroke #import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path diff --git a/src/draw/grouping.typ b/src/draw/grouping.typ index ed6182715..f92466d21 100644 --- a/src/draw/grouping.typ +++ b/src/draw/grouping.typ @@ -195,24 +195,33 @@ /// creates an anchor. Transformations insides the body are scoped and do /// not get applied outsides. /// -/// - name (string): Anchor name. +/// - name (str): Anchor name. /// - reference-point (coordinate): Coordinate to find the closest point to. -/// - body (element): One or more elements to consider. A least one is required. A function that accepts `ctx` and returns elements is also accepted. -#let closest-point(name, reference-point, body) = { +/// - body (element,str): One or more elements to consider. A least one is required. A function that accepts `ctx` and returns elements is also accepted. If a string is passed, the existing named element is used. +#let find-closest-point(name, reference-point, body) = { import "/src/bezier.typ": cubic-closest-point assert(type(name) == str, message: "Anchor name must be of type string, got " + repr(name)) + assert(type(body) in (array, function, str), + message: "Expected body to be a list of elements, a callback or an elements name") coordinate.resolve-system(reference-point) return (ctx => { let (_, pt) = coordinate.resolve(ctx, reference-point) pt = util.apply-transform(ctx.transform, pt) - let group-ctx = ctx - group-ctx.groups.push(()) - let (ctx: group-ctx, drawables, bounds) = process.many(group-ctx, util.resolve-body(ctx, body)) - ctx.nodes += group-ctx.nodes + let (sub-ctx, drawables, output-drawables) = if type(body) == str { + let node = ctx.nodes.at(body) + (ctx, node.drawables, false) + } else { + let group-ctx = ctx + group-ctx.groups.push(()) + let node = process.many(group-ctx, util.resolve-body(ctx, body)) + (node.ctx, node.drawables, true) + } + + ctx.nodes += sub-ctx.nodes let min = calc.inf let min-pt = none @@ -269,8 +278,7 @@ ctx: ctx, name: name, anchors: anchors, - drawables: drawables, - bounds: bounds + drawables: if output-drawables { drawables } else { () }, ) },) } diff --git a/tests/closest-point/ref/1.png b/tests/closest-point/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..b304a114f288ed4834328944607bac448e087f3b GIT binary patch literal 10598 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 literal 0 HcmV?d00001 diff --git a/tests/closest-point/test.typ b/tests/closest-point/test.typ new file mode 100644 index 000000000..5fc1ea026 --- /dev/null +++ b/tests/closest-point/test.typ @@ -0,0 +1,36 @@ +#set page(width: auto, height: auto) +#import "/tests/helper.typ": * + +#test-case({ + import cetz.draw: * + + group(name: "g", { + rotate(10deg) + rect((-1, -1), (1, 1), radius: .45) + }) + + for i in range(0, 360, step: 10) { + let pt = (i * 1deg, 2) + + find-closest-point("test", pt, { + rotate(10deg) + hide(rect((-1, -1), (1, 1), radius: .45)) + }) + + line(pt, "test") + circle(pt, radius: .1, fill: blue) + } +}) + +#test-case({ + import cetz.draw: * + + group(name: "g", { + rotate(10deg) + rect((-1, -1), (1, 1), radius: .45) + }) + + let pt = (2, 2) + find-closest-point("test", pt, "g") + line("test", pt) +})