diff --git a/src/draw.typ b/src/draw.typ index 1e46b9af9..64d367616 100644 --- a/src/draw.typ +++ b/src/draw.typ @@ -1,4 +1,4 @@ #import "draw/grouping.typ": intersections, group, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks #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, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, shadow +#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, shadow diff --git a/src/draw/shapes.typ b/src/draw/shapes.typ index e88bf9491..8a55c2f2f 100644 --- a/src/draw/shapes.typ +++ b/src/draw/shapes.typ @@ -304,6 +304,52 @@ },) } +#let arc-through( + a, + b, + c, + name: none, + ..style, +) = get-ctx(ctx => { + let (ctx, a) = coordinate.resolve(ctx, a) + let (ctx, b) = coordinate.resolve(ctx, b) + let (ctx, c) = coordinate.resolve(ctx, c) + assert(a.at(2) == b.at(2) and b.at(2) == c.at(2), + message: "The z coordinate of all points must be equal, but is: " + repr((a, b, c).map(v => v.at(2)))) + + let center = util.calculate-circle-center-3pt(a, b, c) + let radius = vector.dist(center, a) + let start = { + let (x, y, ..) = vector.sub(a, center) + calc.atan2(x, y) // Typst's atan2 is (x,y) order! + } + let delta = vector.angle(a, center, c) + + let side-on-line(a, b, pt) = { + let (x1, y1, ..) = a + let (x2, y2, ..) = b + let (x, y, ..) = pt + return (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1) + } + + let center-is-left = side-on-line(a, c, center) < 0 + let b-is-left = side-on-line(a, c, b) < 0 + + // If the center and point b are on the same side of a-c, + // the arcs delta must be > 180deg + if center-is-left == b-is-left { + delta = 360deg - delta + } + + // If b is left of a-c, swap a-c to c-a by using a negative delta + if b-is-left { + delta *= -1 + } + + return arc(a, start: start, delta: delta, radius: radius, + anchor: "arc-start", name: name, ..style) +}) + #let mark(from, to, ..style) = { assert.eq( style.pos(), diff --git a/src/util.typ b/src/util.typ index 463dbe0bb..fed80e9c4 100644 --- a/src/util.typ +++ b/src/util.typ @@ -89,7 +89,8 @@ return (calc.cos(angle) * rx + x, calc.sin(angle) * ry + y, z) } -/// Calculate circle center from 3 points +/// Calculate circle center from 3 points. The z coordinate +/// is taken from point a. /// /// - a (vector): Point 1 /// - b (vector): Point 2 @@ -129,11 +130,11 @@ } let x = (d - c)/(a - b) let y = a * x + c - return (x, y, 0) + return (x, y) } assert(args.len() == 4, message: "Could not find circle center") - return line-intersection-2d(..args) + return vector.as-vec(line-intersection-2d(..args), init: (0, 0, a.at(2))) } #let resolve-number(ctx, num) = { diff --git a/tests/arc/arc-through/ref.png b/tests/arc/arc-through/ref.png new file mode 100644 index 000000000..b851a1935 Binary files /dev/null and b/tests/arc/arc-through/ref.png differ diff --git a/tests/arc/arc-through/test.typ b/tests/arc/arc-through/test.typ new file mode 100644 index 000000000..2bc213166 --- /dev/null +++ b/tests/arc/arc-through/test.typ @@ -0,0 +1,65 @@ +#set page(width: auto, height: auto) +#import "/src/lib.typ": * + +#let show-points(..pts) = { + import draw: * + for pt in pts.pos() { + circle(pt, radius: .1) + } +} + +#let test(a, b, c) = { + import draw: * + group({ + anchor("center", (0,0,0)) + show-points(a, b, c) + arc-through(a, b, c) + }, name: "g", anchor: "west", padding: .1) + set-origin("g.east") +} + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + test((0,0), (1, 1), (2, 0)) + test((0,0), (1,-1), (2, 0)) + test((0,1), (1, 0), (0,-1)) + test((0,1), (-1,0), (0,-1)) +})) + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + for a in range(36, 360 + 36, step: 36) { + let a = a * 1deg + test((1,0), + (calc.cos(a / 2), calc.sin(a / 2)), + (calc.cos(a), calc.sin(a))) + } +})) + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + for d in range(0, 8 + 1) { + let d = (d - 2) / 5 + test((0,0), (1,d), (2,.5)) + } +})) + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + // The style radius must not influence the + // arc radius! + set-style(radius: 5) + set-style(arc: (radius: 5)) + test((0,0), (1, 1), (2, 0)) +})) + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + arc-through((0,0), (1,1), (2,0)) + arc-through((0,0,1), (1,1,1), (2,0,1), stroke: blue) +})) diff --git a/tests/arc/ref.png b/tests/arc/ref.png index ee641d84b..d206565e2 100644 Binary files a/tests/arc/ref.png and b/tests/arc/ref.png differ diff --git a/tests/arc/test.typ b/tests/arc/test.typ index eb9563fa6..8e37c685d 100644 --- a/tests/arc/test.typ +++ b/tests/arc/test.typ @@ -34,3 +34,11 @@ content((rel: (0, .5), to: "c." + a), [#a], frame: "rect", fill: white, stroke: none) }) })) + + +#box(stroke: 2pt + red, canvas(length: 1cm, { + import draw: * + + arc((0,0), start: 45deg, delta: 90deg) + arc((0,0,1), start: 45deg, delta: 90deg, stroke: blue) +}))