Skip to content

Commit

Permalink
shapes: Add arc-through
Browse files Browse the repository at this point in the history
Thanks to Matthew Brown for the implementation.
  • Loading branch information
johannes-wolf committed Nov 22, 2023
1 parent 9e042d3 commit f44cba2
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 4 deletions.
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, 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
46 changes: 46 additions & 0 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
7 changes: 4 additions & 3 deletions src/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) = {
Expand Down
Binary file added tests/arc/arc-through/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions tests/arc/arc-through/test.typ
Original file line number Diff line number Diff line change
@@ -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)
}))
Binary file modified tests/arc/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions tests/arc/test.typ
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}))

0 comments on commit f44cba2

Please sign in to comment.