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

arc: Add a function arc-through #206

Closed
johannes-wolf opened this issue Sep 21, 2023 · 6 comments · Fixed by #273
Closed

arc: Add a function arc-through #206

johannes-wolf opened this issue Sep 21, 2023 · 6 comments · Fixed by #273
Labels
Milestone

Comments

@johannes-wolf
Copy link
Member

johannes-wolf commented Sep 21, 2023

That function should connect two points with an arc touching the third point (see circle-through).

@fenjalien
Copy link
Member

We could allow passing 3 positional arguments in the current arc function? When 3 are dectected it draws an arc through them. We could extend this to circle-through

@johannes-wolf
Copy link
Member Author

I think overloading existing functions too much only brings complexity with no real benefit. I would favor having the extra *-through functions.

@fenjalien
Copy link
Member

what would be best is allowing dot access notation on the element function. for example circle(...) and circle.through(...). But this can't be done until typst structs or custom elements are implemented. So I guess *-through would be okay... In terms of complexity though, they could be implemented by transforming the three points into a radius and position and calling the primative element function.

@johannes-wolf
Copy link
Member Author

Yes, if the dot syntax becomes available we can switch to that.

@matthew-e-brown
Copy link
Contributor

matthew-e-brown commented Oct 15, 2023

Would you two be okay with it if I had a go at implementing this? I know you're in the middle of a rework, so I feel like I should ask before making a PR.

I've already gotten the basics working using what CetZ provides out of the box. I would just need to convert it to the same style that the other functions use, where they return that fancy dictionary with custom-anchors and whatnot.

#let arc-through(a, b, c, ..args) = cetz.draw.get-ctx(ctx => {
    import cetz.vector
    import cetz.coordinate

    let a = coordinate.resolve(ctx, a)
    let b = coordinate.resolve(ctx, b)
    let c = coordinate.resolve(ctx, c)

    let center = coordinate.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)

    cetz.draw.arc(a, ..args, start: start, delta: delta, radius: radius, anchor: "start")
})

arc-through

Let me know what you think. I'll hold off if it should wait until the rework is complete.


Related to arcs/curves, I'd also like to take a stab at #239 at some point, if that isn't going to be part of the rework, as well... It just so happens that my latest Algorithms assignment fits really well with drawing curved arrows 😅

@johannes-wolf
Copy link
Member Author

johannes-wolf commented Oct 15, 2023

Thank you for the work! There are some edge cases where your implementation did not work correctly. We have to check on which side of a-c, b and center lay and swap the delta if needed. I hope this version works as expected.

#import "@preview/cetz:0.1.2"
#set page(width: auto, height: auto)

#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 arc-through(a, b, c, ..args) = cetz.draw.get-ctx(ctx => {
    import cetz.vector
    import cetz.coordinate

    let a = coordinate.resolve(ctx, a)
    let b = coordinate.resolve(ctx, b)
    let c = coordinate.resolve(ctx, c)

    let center = coordinate.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 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
    }

    cetz.draw.content(center, [#delta])
    cetz.draw.arc(a, ..args, start: start, delta: delta, radius: radius, anchor: "start")
})

#let test(a, b, c) = {
  import cetz.draw: *
  group({
    anchor("default", (0,0))
    circle(a, radius: .1, fill: green)
    circle(b, radius: .1, fill: yellow)
    circle(c, radius: .1, fill: red)
    arc-through(a, b, c, name: "a")
  }, name: "g", anchor: "left")

  on-layer(-1, rect("g.bottom-left", "g.top-right", stroke: none, fill: gray.lighten(80%)))
  set-origin((rel: (1,0), to: "g.right"))
}

#cetz.canvas({
  import cetz.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))
  test((0,0), (1,3), (4,0))
  test((0,0), (1,3), (4,3))
  test((1,0), (2,3), (0,3))
  test((0,0), (1,-3), (4,0))
  test((0,0), (1,-3), (4,3))
})

grafik

You can create a PR, but I would wait to merge it until the rework has been merged.

@fenjalien fenjalien linked a pull request Oct 23, 2023 that will close this issue
@johannes-wolf johannes-wolf added this to the 0.2 milestone Oct 30, 2023
johannes-wolf added a commit that referenced this issue Dec 4, 2023
Fixes #206. Thanks to @matthew-e-brown for the initial implementation!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants