Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draw: Function for finding closest point #668

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Find closest point by name
johannes-wolf committed Nov 27, 2024
commit ceb577be021b20cc11f7ed59c74fd155b0ca069b
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -41,7 +41,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
10 changes: 8 additions & 2 deletions src/bezier.typ
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/draw.typ
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, hide, floating
#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, 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, register-mark
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path
26 changes: 17 additions & 9 deletions src/draw/grouping.typ
Original file line number Diff line number Diff line change
@@ -194,24 +194,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
@@ -268,8 +277,7 @@
ctx: ctx,
name: name,
anchors: anchors,
drawables: drawables,
bounds: bounds
drawables: if output-drawables { drawables } else { () },
)
},)
}
Binary file added tests/closest-point/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions tests/closest-point/test.typ
Original file line number Diff line number Diff line change
@@ -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)
})