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

Round join and all cap styles #414

Merged
merged 5 commits into from
Nov 22, 2023
Merged

Conversation

armansito
Copy link
Collaborator

Implemented the logic for round join and cap styles. The logic generates lines directly from a circular arc. The arc gets flattened in the curve's local coordinate space and gets transformed to device-space coordinates post-flattening.

This completes the initial implementation of all stroke styles. This PR is based on #412.

#303

@armansito armansito requested a review from dfrg November 16, 2023 23:44
@@ -280,39 280,95 @@ fn flatten_cubic(cubic: Cubic, local_to_device: Transform, offset: f32) {
}
}

// Flattens the circular arc that subtends the angle begin-center-end. It is assumed that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not obvious from this comment how the direction is chosen. It must be based on the value of angle, but it feels like 'pacman' style arcs will be difficult.

I worry that if n_lines is big, the arc would draw the full way around, but if n_lines is small, it would end up drawing the closing part, rather than the full way around

I'm not sure that this is coherent - my last maths education was many years ago.

Maybe this would only be an issue if n_lines is 1, which is impossible?

Copy link
Collaborator Author

@armansito armansito Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction of the arc is always a counter-clockwise rotation starting from begin, towards end, centered at center, and will be subtended by angle (which is assumed to be positive). If angle terminates the arc before it smoothly reaches end, a line will be drawn from the end of the arc towards end. I will document this.

n_lines only depends on the radius of the arc and the chosen tolerance. For n_lines to be $1$, theta must be close to $6.2831853$ ($2\pi$). acos can't return that by definition. If you somehow got n_lines == 1, basically no arc would be drawn and you'd get a line connecting begin and end.

Realistically, n_lines could be as low as $2$ if the absolute value of x is very small, which can happen if tol and radius are close to each other (the code prevents radius from being smaller than tol, so that case isn't possible). You'd only get into that situation with a sub-pixel radius. At that scale, the coarseness of the approximation won't be easily noticeable. If you increased the tolerance to make this possible at larger radii, with n_lines == 2 you'd get a shape resembling a wedge/triangle (I tested this locally, which works as you'd expect).

That said, this function is meant for drawing caps and joins where angle$\le\pi$ and the begin, center, and end points are chosen carefully (so that the angle and winding make sense).

Though there is something that should get fixed: I think selecting 1u when theta <= EPS is the wrong thing to do and this is a bug. That happens when the subdivision count tends to infinity. It's probably better to define a MAX_LINES and return that instead.

Copy link
Collaborator Author

@armansito armansito Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To illustrate what I mean, here is what the code draws if I center the center of a round join directly below the join control point and hardcode an angle of $343.77\degree$:

  • n_lines == 1:
Screenshot 2023-11-17 at 12 10 01 PM
  • n_lines == 2:
Screenshot 2023-11-17 at 12 10 44 PM
  • n_lines == 3:
Screenshot 2023-11-17 at 12 11 29 PM
  • n_lines == 6:
Screenshot 2023-11-17 at 12 12 18 PM
  • n_lines == 100:
Screenshot 2023-11-17 at 12 12 58 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the very detailed reply!

I hadn't clocked the additional invariants being maintained by the callers

Those pictures are very cool as well!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad they were helpful 🙂. I updated the comment above flatten_arc in latest commit.

Copy link
Collaborator

@dfrg dfrg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Exciting to see this all coming together!

Rather than using the `Cubic` struct, the flattening helpers operate on
`CubicPts`, path index, and parallel curve offset directly.

The older stroke bbox computation was removed since it's no longer used.
Implemented the logic for round join and cap styles. The logic generates
lines directly from a circular arc. The arc gets flattened in the
curve's local coordinate space and transformed to device-space
coordinates post-flattening (just like strokes work in `flatten_arc`).

An advantage of flattening an arc directly is that the logic is very
simple and involves way less ALU compared to `flatten_cubic`. The
disadvantage is that the structure of join/cap style handling is now
highly divergent.

To reduce divergence, the shader could be restructured to model a (not
perfectly circular) arc with cubic Beziers and let the control flow
converge at a call to `flatten_cubic`.
Implement butt/square/round start and end cap styles
If the segment angle falls below epsilon, return MAX_LINES instead
of 1u for the line segment count. Total number of lines per arc is now
capped at 1000.
@armansito armansito force-pushed the gpu-strokes-2-caps-and-round-join branch from 8dc91f5 to bae1d1a Compare November 22, 2023 00:38
@armansito armansito changed the base branch from gpu-strokes-1-cpu-dashing to main November 22, 2023 00:38
Clarified the function's behavior and input invariants
@armansito armansito merged commit f34d383 into main Nov 22, 2023
4 checks passed
@armansito armansito deleted the gpu-strokes-2-caps-and-round-join branch November 22, 2023 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants