|
191 | 191 | },)
|
192 | 192 | }
|
193 | 193 |
|
| 194 | +/// Finds the closest point on one or more elements to a coordinate and |
| 195 | +/// creates an anchor. Transformations insides the body are scoped and do |
| 196 | +/// not get applied outsides. |
| 197 | +/// |
| 198 | +/// - name (string): Anchor name. |
| 199 | +/// - reference-point (coordinate): Coordinate to find the closest point to. |
| 200 | +/// - body (element): One or more elements to consider. A least one is required. A function that accepts `ctx` and returns elements is also accepted. |
| 201 | +#let closest-point(name, reference-point, body) = { |
| 202 | + import "/src/bezier.typ": cubic-closest-point |
| 203 | + |
| 204 | + assert(type(name) == str, |
| 205 | + message: "Anchor name must be of type string, got " + repr(name)) |
| 206 | + coordinate.resolve-system(reference-point) |
| 207 | + |
| 208 | + return (ctx => { |
| 209 | + let (_, pt) = coordinate.resolve(ctx, reference-point) |
| 210 | + pt = util.apply-transform(ctx.transform, pt) |
| 211 | + |
| 212 | + let group-ctx = ctx |
| 213 | + group-ctx.groups.push(()) |
| 214 | + let (ctx: group-ctx, drawables, bounds) = process.many(group-ctx, util.resolve-body(ctx, body)) |
| 215 | + ctx.nodes += group-ctx.nodes |
| 216 | + |
| 217 | + let min = calc.inf |
| 218 | + let min-pt = none |
| 219 | + |
| 220 | + // Compute the closest point on line a-b to point pt |
| 221 | + let line-closest-pt(pt, a, b) = { |
| 222 | + let n = vector.sub(b, a) |
| 223 | + let d = vector.dot(n, pt) |
| 224 | + d -= vector.dot(a, n) |
| 225 | + |
| 226 | + let f = d / vector.dot(n, n) |
| 227 | + return if f < 0 { |
| 228 | + a |
| 229 | + } else if f > 1 { |
| 230 | + b |
| 231 | + } else { |
| 232 | + vector.add(a, vector.scale(n, f)) |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + for d in drawables { |
| 237 | + if not "segments" in d { continue } |
| 238 | + |
| 239 | + for ((kind, ..pts)) in d.segments { |
| 240 | + if kind == "cubic" { |
| 241 | + let tmp-pt = cubic-closest-point(pt, ..pts) |
| 242 | + let tmp-min = vector.dist(tmp-pt, pt) |
| 243 | + if tmp-min < min { |
| 244 | + min-pt = tmp-pt |
| 245 | + min = tmp-min |
| 246 | + } |
| 247 | + } else { |
| 248 | + for i in range(1, pts.len()) { |
| 249 | + let tmp-pt = line-closest-pt(pt, pts.at(i - 1), pts.at(i)) |
| 250 | + let tmp-min = vector.dist(tmp-pt, pt) |
| 251 | + if tmp-min < min { |
| 252 | + min-pt = tmp-pt |
| 253 | + min = tmp-min |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + let (transform, anchors) = anchor_.setup( |
| 261 | + anchor => min-pt, |
| 262 | + ("default",), |
| 263 | + default: "default", |
| 264 | + name: name, |
| 265 | + transform: none |
| 266 | + ) |
| 267 | + |
| 268 | + return ( |
| 269 | + ctx: ctx, |
| 270 | + name: name, |
| 271 | + anchors: anchors, |
| 272 | + drawables: drawables, |
| 273 | + bounds: bounds |
| 274 | + ) |
| 275 | + },) |
| 276 | +} |
| 277 | + |
194 | 278 | /// Groups one or more elements together. This element acts as a scope, all state changes such as transformations and styling only affect the elements in the group. Elements after the group are not affected by the changes inside the group.
|
195 | 279 | ///
|
196 | 280 | /// ```typc example
|
|
0 commit comments