Open GregStanton opened 10 months ago
Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. Thank you!
Thanks @GregStanton for the suggestion. I'm inviting the current p5.js Core Stewards to this discussion @limzykenneth, @davepagurek, @ChihYungChang, @teragramgius, @tuminzee, @Zarkv, @robin-haxx, @Gaurav-1306. Please feel free to leave your comments about this feature request. Thank you!
Great suggestion.
I would like to work on this one. Let me get back with details of implementation.
Amazing! Thank you so much @Gaurav-1306! And thank you to @Qianqianye for bringing the others into the conversation!
Verbal version of the tutorial
In case it helps anyone, I'll elaborate slightly on the tutorial. We want to draw an arc along the specified ellipse, from the first vertex to the second vertex. But we need to decide how to do that. We can start by imagining moving the ellipse onto the two vertices. There are two ways to do this; in the example shown, we get one copy of the ellipse attached from below and one attached from above. The vertices divide each ellipse copy into two arcs (major and minor). This means that there are four possible arcs. The type
and direction
parameters allow the user to specify which one they want (e.g. the minor clockwise arc is one option). Here's a quick p5 sketch that shows how the type and direction parameters allow you to specify four possible arcs (with different colors for each arc).
Specification for out-of-range parameters It seems like a good idea to be consistent with how the SVG specification handles parameters that are out of range. This comes from the W3C Candidate Recommendation for SVG 2.
I hope this helps!
I think this makes sense as a way to increase consistency between modes of drawing shapes.
Do you have thoughts on the current API, based on SVG path arc commands, vs something more like the vanilla J's canvas arcTo
API? https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#elliptical_arc_curve The API for this one seems a bit more approachable at first glance (although your naming is much clearer than the flag names in the SVG spec!)
Thanks for your feedback @davepagurek! I'm glad you like the naming. I'm also glad you asked about other APIs. I asked myself the same question, and I think it's good to document the rationale here.
The problem with SVG's arc command, and how to mitigate it It's commonly acknowledged that SVG's arc command is not the simplest. Yet, it does work, and we can mitigate any difficulties. The friendlier API I proposed should help. It should also be possible to improve the graphic tutorial I shared.
The problem with canvas's arcTo()
, and why we can't mitigate it
A circular arc made with arcTo()
may end at an unspecified point. (Really, arcToward()
is a more accurate name than arcTo()
.) This conflicts with p5's functions, which are explicitly vertex based[^1]: quadraticVertex()
, bezierVertex()
etc. all have "vertex" in the name, and the eponymous vertex is always represented by the final parameters.
The problem with canvas's ellipse()
, and why we can't mitigate it
The native canvas API also has a method that creates an elliptical arc as a sub-path. It's simply called ellipse()
. However, its API has the same problem as arcTo()
's.[^2]
The benefits of SVG's arc command
SVG's arc command is vertex based, so it's consistent with p5's commands. By adopting a version of it, p5 is not just internally consistent, but it's also consistent with all of SVG's path commands.[^3] This has benefits for both users and library developers. Users familiar with SVG will immediately understand how arcVertex()
works, and library developers should be able to directly translate SVG path commands to p5 vertex commands.
[^1]: The curveVertex()
function of p5 introduces a subtle variation, in which the vertex is actually a control point (it guides the curve, but the curve does not pass through it). Nonetheless, with a separate function call, the user is able to specify where the curve will end.
[^2]: The SVG arc command and the canvas ellipse()
method implement two different parameterizations of elliptical arcs. The the SVG implementation notes helpfully refer to these as the endpoint parameterization and the center parameterization, respectively. I thought a little about whether a simpler endpoint parameterization is possible, but I'm not sure if it could be simple enough to justify diverging from both the canvas and SVG APIs.
[^3]: Internal consistency is actually cited in the SVG implementation notes as a reason for the design of its arc command. The notes are clearly written and address the same issue that we face.
That makes sense. In the MDN example, they also follow up arcTo with lineTo to ensure it always ends at the target point. That might be a way of using an API like the native canvas one while still preserving the quality of ending at the specified point?
Aside from the API of arcTo appearing simpler, I'm mostly considering it further because I assume we'll have to end up translating our API into an arcTo command for 2D mode anyway. Although it'll have to be turned into line segments for WebGL, so we'll be doing that part from scratch regardless. I don't have a strong preference, so if there are more issues with a combined arcTo + lineTo, I'm happy to go with the API that you suggested!
@davepagurek I really appreciate you helping to vet this idea! I think it's always good to thoroughly vet an addition to an API, especially a core feature like this.
I'll respond with some additional downsides of arcTo()
that I see, followed by one approach that may address your concern about the implementation of arcVertex()
.
Downsides of arcTo()
Here are a few issues in addition to it not ending at a specified point:
lineTo()
, since the arc may need to be connected to the most recent path point with a line segment. So, it's not a curve primitive in the same way that a pure circular arc is.In general, I think arcTo()
is mainly provided as a more convenient way to round corners. Its behavior can be achieved in principle with canvas's ellipse()
and lineTo()
methods. The more general, primitive method is ellipse()
, which actually draws not just ellipses but also elliptical arcs. My feeling is that the priority is to add general primitives first.[^2]
Implementing arcVertex()
Instead of using arcTo()
for 2D mode, one option is to convert the endpoint parameterization of SVG's arc command to the center parameterization of canvas's ellipse()
, and then draw it with canvas's ellipse()
.[^3] For the mathematical details on this exact conversion, see the SVG implementation notes. The authors of the SVG spec anticipated the need to convert to other APIs.
As always, I'm eager to hear feedback on any of this!
[^1]: For example, the p5.teach add-on library by @two-ticks adds a dependency (anime.js) in order to animate typeset math expressions in the style of Manim. It uses MathJax to convert the expressions to SVG, which is then animated with anime.js. I suspect this requirement could be satisfied with p5 alone if it were possible to reproduce SVG paths with beginShape()
/endShape()
. I'm very curious to hear others' thoughts on this.
[^2]: If we want to add a utility function that creates composite paths like arcTo()
, its convenience would need to be weighed against the complication it introduces: it wouldn't correspond neatly to any p5 curve primitive. The convenience might well outweigh that complication! But it's something to consider.
[^3]: When I was looking into this originally, one thing that surprised me is that p5's own arc()
and ellipse()
are not implemented with the canvas's ellipse()
. The implementation approximates the ellipse with Beziér curves. Maybe this was easier to use with WebGL, since the Beziér curves had already been adapted for that?
Instead of using arcTo() for 2D mode, one option is to convert the endpoint parameterization of SVG's arc command to the center parameterization of canvas's ellipse(), and then draw it with canvas's ellipse(). For the mathematical details on this exact conversion, see the SVG implementation notes.
Thanks for the link, this will be helpful for implementing later! It looks like ellipse()
will be able to do what we need it to.
It only makes circular arcs, rather than elliptical arcs, so it's less general. If we try to generalize it and also take away the linear pieces, we end up with something like the SVG arc command.
This is a good point. Do you think we can make a version of the API where the elliptical aspects of the command come last and are optional, so that if they're missing we can default them to circular arcs? I assume that will likely be the most common use.
When I was looking into this originally, one thing that surprised me is that p5's own arc() and ellipse() are not implemented with the canvas's ellipse(). The implementation approximates the ellipse with Beziér curves. Maybe this was easier to use with WebGL, since the Beziér curves had already been adapted for that?
I'm actually not sure why the 2D mode uses Bezier paths. The WebGL mode version just uses trig functions.
Replying to https://github.com/processing/p5.js/issues/6459#issuecomment-1764488128 by @davepagurek:
Thanks for the link, this will be helpful for implementing later! It looks like ellipse() will be able to do what we need it to.
Sure thing! I'm glad that helped.
Do you think we can make a version of the API where the elliptical aspects of the command come last and are optional, so that if they're missing we can default them to circular arcs? I assume that will likely be the most common use.
The arcVertex
API that I proposed accommodates the circle case in the same way that p5's arc
does. As in the first example of the p5 reference page for arc
, the user can create circular arcs by choosing the width and height parameters to be equal. I think it's nice to be as consistent as possible, so this seems like a good solution to me.
The only other option that's obvious to me is to do something like p5's ellipse
. In that case, it's possible to omit the height parameter, in which case it works just like circle
, since the height is automatically set equal to the width. If we try this with arcVertex
by moving the width parameter to the end, we'd introduce inconsistencies. Most significantly, arcVertex
wouldn't end in vertex parameters like p5's other vertex functions.
I'm actually not sure why the 2D mode uses Bezier paths. The WebGL mode version just uses trig functions.
I got curious, so I did a little digging. It looks like the use of Bézier curves in p5's 2D implementation of arc
and ellipse
dates to p5's addition of ellipse
on July 4, 2013. At that time, the canvas didn't have a native ellipse
method.[^1]
Would it make sense to create a separate issue to update p5's implementation of arc
and ellipse
, as part of modernization efforts? The current implementation is complicated enough that the source comments include a link to a paper on the underlying math; using the canvas's native ellipse
should be a pretty significant simplification.
[^1]: According to MDN's browser compatibility table for CanvasRenderingContext2D, bezierCurveTo
was implemented in browsers as early as 2005, whereas the ellipse
method didn't appear in any of the listed browsers until November 2013.
I think that all makes sense, thanks for bearing with me and fur such thorough explanations! I think this API looks good to me then.
For the 2d implementation of ellipse, making another issue for that would be great, thanks!
Replying to https://github.com/processing/p5.js/issues/6459#issuecomment-1767350565 by @davepagurek:
I think that all makes sense, thanks for bearing with me and fur such thorough explanations! I think this API looks good to me then.
Absolutely! I'm glad I'm not the only one considering the design from various perspectives.
For the 2d implementation of ellipse, making another issue for that would be great, thanks!
Done: #6485
Do you have a recommendation for the next step? I'm not sure if @Gaurav-1306 has been following the discussion, but they expressed interest in working on the implementation. Hopefully, our discussion will help with that. Here are the most relevant parts:
Specification for out-of-range parameters It seems like a good idea to be consistent with how the SVG specification handles parameters that are out of range. This comes from the W3C Candidate Recommendation for SVG 2.
Implementing
arcVertex()
One option is to convert the endpoint parameterization of SVG's arc command to the center parameterization of canvas'sellipse()
, and then draw it with canvas'sellipse()
. For the mathematical details on this exact conversion, see the SVG implementation notes.
Hey I have been following your discussions and would still like to implement this.
Let me understand all that you guys discussed and get back with the implementation details.
Just a little update
I have been really busy with my university exams. I will start working on this from the day after tomorrow.
I will get back on it really soon. Thanks for the patience 😁
hello
So I have been reading your discussions. I have a few doubts that can help me write a better code.
Provide a new function called
arcVertex()
based on SVG’s arc command. Applications include the following:
@GregStanton in your original implementation detail you stated to use the SVG arc command but after the discuss has been over, the conclusion was to use canvas's api
Instead of using
arcTo()
for 2D mode, one option is to convert the endpoint parameterization of SVG's arc command to the center parameterization of canvas'sellipse()
, and then draw it with canvas'sellipse()
.3 For the mathematical details on this exact conversion, see the SVG implementation notes. The authors of the SVG spec anticipated the need to convert to other APIs.
ellipse()
to draw the actual arc. Also in loss terms when we need to draw a circle, we just equal the value of height and width.
are we on the same page?Now regarding the 2D and 3D implementation of the function, do we need to add more parameter(a third vertex to know the location to end in 3D space) to the function or this implementation detail can be understood with SVG api? my doubt basically is how are we planning to implement the 2d and the 3d versions together?
again sorry for so many doubts and thanks for the help!
Hi @Gaurav-1306!
Thanks for taking a look, and I appreciate your questions! The short answer: I originally suggested that we base the arcVertex()
API on SVG, and that is still our plan; later, I proposed one way to implement arcVertex()
using a native canvas method. I know that may be confusing, and you had several questions. So, I'll try to explain everything in a longer answer below. If you have any follow-up questions, please don't hesitate to ask!
Elliptical arcs can be drawn with SVG. They can also be drawn with the native canvas API. However, SVG and the canvas use different sets of parameters.
arcVertex()
: API vs. implementationFor arcVertex()
in p5, the user will enter one parameterization and the implementation will use a different one.
arcVertex()
work will use the native canvas API, since p5 draws shapes to the canvas.This means that we'll have to convert from one parameterization to the other.
I'll describe two possible approaches for the conversion from the endpoint parameterization to the center parameterization. In the preceding comments, I only described the first option, but the second option is likely easier.
ellipse()
method (that method draws full ellipses and elliptical arcs along part of an ellipse).Path2D()
constructor accepts SVG path definitions as input, and it outputs a path that can be drawn to the canvas. [^2] In other words, it does the conversion for us. In Option 2, you'd still need to convert the arcVertex
arguments into an SVG path definition; however, that basically just requires you to combine them into a string with the correct format. [^3] Here's a quick sketch I made that illustrates the use of Path2D()
for drawing elliptical arcs from SVG path data.
For the sake of completeness, I'll include the earlier point about out-of-range parameters:
It seems like a good idea to be consistent with how the SVG specification handles parameters that are out of range. This comes from the W3C Candidate Recommendation for SVG 2.
I'll share my current understanding, but I think @davepagurek will be able to help more with this.
To start, p5 has separate renderers for 2D (p5.Renderer2D) and 3D (p5.RendererGL). SVG paths and the Path2D
paths only apply to 2D. If you want to just start with the 2D implementation, I think that would be great! For 3D, I suspect we'd do Option 1 (the mathematical conversion from the endpoint parameterization to the center parameterization); then I imagine we'd use cosine and sine to generate a sequence of vertices along the ellipse, and connect those with tiny approximating line segments.
Finding where to put your code is another issue. The vertex functions are defined in vertex.js. For the 3D versions, vertex.js calls out to the 3D renderer's vertex functions, which are defined in 3d_primitives.js. There may be other relevant code elsewhere in the codebase.
To create a circular arc, the user would just use equal values for the width and the height. as you said. This shouldn't require any extra programming on your part.
[^1]: For the SVG arc command, the starting point is specified separately. Basically, SVG paths are built out of multiple pieces, and the starting point of an elliptical arc is taken to be the endpoint of the most recent piece that was added.
[^2]: The Path2D interface also supports all the usual path methods.
[^3]: When converting the arcVertex()
arguments to an SVG string, you'd include an A
or a
for arc, you'd use 0
and 1
instead of MINOR
and MAJOR
, etc. The MDN reference is a good place to learn about the format.
Thanks, @GregStanton for such a great replay, all my confusion is gone.
I will start working on the 2D implementation as of now. Where to put the code is a good question but I always thought that it should go in vertex.js, I will look more into this.
One last thing, do you think choosing option 1 can help with performance in any way, in option 2 we are using the API two times one for conversation and another one for drawing ellipses. I am comfortable either way but I feel that direct conversation can benefit performance because we are not calling the API two times.
Hello everyone, while I have been working on the implementation I came up with the following code. Do give your feedback. a request form @davepagurek can you explain to me how should I draw on the canvas, more formally when we use Canvas API, we have to call the this._renderer._context.stroke
and make sure that it gets rendered on the canvas. I wanted to ask which .html file is responsible for the final rendering. the following snippet may make it more clear
p5.prototype.arcVertex = function(w, h, angle, type, direction, x2, y2) {
p5._validateParameters('arcVertex', arguments);
// Convert endpoint parameterization to SVG path definition
const arcPath = `M ${x1} ${y2} A ${w} ${h} ${angle} 0 ${type === MAJOR ? 1 : 0} ${direction === COUNTERCLOCKWISE ? 1 : 0} ${x2} ${y2}`;
// Create a Path2D object from the SVG path definition
const path2D = new Path2D(arcPath);
// i think this is where the draw the ellipse from canvas api will go.
// Draw the path to the canvas
this._renderer._context.stroke(path2D);
return this;
};
@davepagurek I have been working on the implementation and I came up with a function. I think the logic is right here, but you can double-check it once.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
p5.prototype.arcVertex = function(w, h, angle, type, direction, x2, y2) {
const x1 = vertex[0];
const y1 = vertex[1];
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
const path = new Path2D();
const rotation = angle * (Math.PI / 180);
const radiusX = type === 'MAJOR' ? w / 2 : h / 2;
const radiusY = type === 'MAJOR' ? h / 2 : w / 2;
const startAngle = Math.atan2(y1 - centerY, x1 - centerX);
const endAngle = Math.atan2(y2 - centerY, x2 - centerX);
path.ellipse(centerX, centerY, radiusX, radiusY, rotation, startAngle, endAngle, direction === 'COUNTERCLOCKWISE');
ctx.stroke(path);
};
there are few think i don't understand, like if i return this
in the code will the p5renderer be able to handle the ctx.stroke, i am saying this because other drawing curves do the same. also where should the code go is codebase. anything that can help with this.
thanks!
@GregStanton can you can pitch in too, need help🙂
Hi @Gaurav-1306!
I'll try to answer your questions by walking you through the steps I took to understand the relevant parts of the codebase. I haven't studied all the code in detail yet, but hopefully this will point you in the right direction.
Note: I wish I could explain these implementation details more concisely! To help you understand it all, I'll include links to the exact lines of code that I refer to, when possible.
We can start by investigating the implementations of the existing functions: beginShape()
, endShape()
, vertex()
, quadraticVertex()
, bezierVertex()
, and curveVertex()
. These are all defined in vertex.js. However, when we read their definitions, we notice that these functions don't directly draw anything. They do contain logic that separates the 2D and 3D cases. Let's consider these cases one at a time.
The main job of each function:
beginShape()
: This clears any data from previous shapes and sets the mode (POINTS, LINES, etc.).vertex()
, quadraticVertex()
, bezierVertex()
, and curveVertex()
: These store vertex data for later use.endShape()
(it also closes the shape if appropriate and resets some values).To be consistent with this organization, you would define arcVertex()
in vertex.js, just like the vertex functions above. The actual drawing code would go elsewhere...
Where the drawing happens:
To understand how things actually get drawn, we need to track down the 2D version of endShape()
that's called from vertex.js. In that file, we see the 2D version of endShape()
is simply called endShape()
; we know it's the 2D version because it belongs to this._renderer
, which in this case is the 2D renderer. To find out what the 2D version of endShape()
does, we can try looking for its definition in p5.Renderer2D.js.
When we find it, we see that it contains a lot of code. However, much of the code is dedicated to the different modes (POINTS, LINES, TRIANGLES, etc.). Those apply only to vertex()
. The modes won't apply to arcVertex()
, so we can ignore that part of the code. The cases most relevant to us are isCurve
, isBezier
, and isQuadratic
. In each of these cases, the corresponding method of the native canvas API is called. You'll most likely add an isArc
case that will call the canvas's native ellipse()
method.
What this means for you: You'll need to put code in multiple files.
arcVertex()
in vertex.js.isArc
at the top of vertex.js and incorporate it into arcVertex()
and endShape()
in the same file.endShape()
in p5.Renderer2D.js, as explained above. Note: The existing implementation seems to contain dead code.
The code repeatedly checks if shapeKind
is equal to constants.POLYGON, but there appears to be no such constant defined in constants.js. If this is old code left over from an earlier implementation, it seems we could simply remove these checks.
Since you're just working on the 2D case (at least for now), I won't say much about this. I'll just mention where most of the relevant code lives, so that we have a convenient reference for both the 2D and 3D cases. Each function in vertex.js calls out to an internal 3D version by the same name, defined on p5.RendererGL, but these definitions are found in different places:
beginShape()
, the 3D renderer's endShape()
, and the 3D renderer's vertex()
are located in p5.RendererGL.Immediate.js.quadraticVertex
, the 3D renderer's bezierVertex()
, and the 3D renderer's curveVertex()
are located in 3d_primitives.js.The 3D versions of quadraticVertex()
, bezierVertex()
, and curveVertex()
call on internal properties and methods of the 3D renderer, such as _bezierCoefficients()
. Those are defined in p5.RendererGL.js.
Earlier, you asked about performance in relation to the two options I proposed above for converting between parameterizations. In both options, the conversion has to occur. It's just a matter of whether you do it yourself, or whether you call a native canvas function that does it for you. I'm not sure that performance is a major consideration here, but others may have more insight. Regarding your code snippets, I'll make separate comments on each of your approaches below.
Automatic conversion: Your snippet with the automatic conversion, which we previously called Option 2, looks like it may work with a few changes:
arcVertex()
. It comes from a call to vertex()
that precedes the call to arcVertex()
.0
preceding the flag parameters.1
.this.drawingContext.stroke(path2D)
in the appropriate place, rather than this.renderer._context.stroke(path2D)
.Mathematical conversion: Your snippet with the mathematical conversion, which we previously called Option 1, is more problematic. One of the problems is that the calculation you're using for centerX
, centerY
gives the midpoint of the segment joining the endpoints, but that isn't always the same as the center of the ellipse. You'll want to use the conversion that's described in the SVG implementation notes. If you haven't learned enough math to understand that yet, let us know.
Thanks so much for working on this! I know I didn't address every detail, but I hope this comment clears up some confusion!
@GregStanton I can't thank you enough you take the time to explain things in so much detail. This is not going to be as easy as I thought it would be. Let me read and understand everything. ✌🏻
@Gaurav-1306 I'm really glad to help!
Hi @Gaurav-1306!
I just wanted to let you know that I submitted issue #6560, which should help us simplify this part of the codebase. It will probably make sense to resolve that issue first. It'll make it easier to implement arcVertex()
. If you were to implement arcVertex()
right now, it'd probably end up needing to be refactored to be consistent with the changes proposed in #6560 anyway.
Also, if you check out the proof of concept that I shared along with that issue, you'll see that I did already include an implementation of arcVertex()
to test out my ideas. But there's still work to be done. The code I wrote only handles the 2D case right now, and it will eventually need to be taken out of the informal proof of concept and incorporated directly into p5.
Thanks for the update @GregStanton, i will work on that issue and then as you said will come back for the arcvector.
Update: Oh, wow. I just had a new idea for a different arcVertex()
API that would be more consistent with #6766.
Specifically, we should be able to let the user specify an elliptical arc with commands as simple as arcVertex(x, y)
. They'd just need to specify where they want it to start and end, along with three intermediate points that they want the arc to pass through. Mathematically, that data is enough to uniquely determine the arc passing through those points. Altogether, it'd look like this:
beginShape();
arcVertex(x0, y0);
arcVertex(x1, y1);
arcVertex(x2, y2);
arcVertex(x3, y3);
arcVertex(x4, y4);
endShape();
(Just for now, I'm ignoring the 3D case and texture coordinates, for simplicity.)
This would be a very nice simplification, and if we adopt the proposal in #6766, it'd mean that arcVertex()
would take the same input as all the other vertex functions. We'd need to think through the implications, though. Some initial questions to consider:
I think this is a cool idea, I like the symmetry it has with other APIs! Maybe for comparison, how would it look to draw a rounded rectangle corner with this API? Does it offload a lot of math onto users to generate the three points in a case like that?
Also, since quadraticVertex
generally starts with a regular vertex
, would your example be something like this to match?
beginShape();
vertex(x0, y0);
arcVertex(x1, y1);
arcVertex(x2, y2);
arcVertex(x3, y3);
arcVertex(x4, y4);
endShape();
Thanks for your feedback @davepagurek! I'll try to answer your questions concisely.
Maybe for comparison, how would it look to draw a rounded rectangle corner with this API? Does it offload a lot of math onto users to generate the three points in a case like that?
Task 1: Round the corner of a rectangle
The original API seems easier to use in this case, as we’d expect, but it's still manageable with the new API for users who know a bit of math. The essential lines of code in each case are copied below, from a couple of quick sketches I made. (The second sketch just uses the point()
function to visualize points that would be passed to arcVertex()
if the new API were implemented.)
Original API: arcVertex(d, d, 0, MINOR, CLOCKWISE, x + d / 2, y);
[full sketch]
New API: arcVertex(cX - r * cos(i * dt), cY - r * sin(i * dt));
[full sketch]
In general, when the user is already thinking of an ellipse in terms of parameters like width, height, and rotation angle, the new API will require them to use extra math to generate points along that ellipse.
Task 2: Click on points, and generate an ellipse that passes through them
This would be simple with the new API, but for most users, it would be prohibitively difficult with the original API.
Summary: Although the new API offloads more math in a common use case, it makes it more likely that users will be able to accomplish both tasks above.
Also, since quadraticVertex generally starts with a regular vertex, would your example be something like this to match?
For the sake of this discussion, I'm assuming we'll implement the proposal for the new vertex function API. In that proposal, there is no quadraticVertex()
, since bezierVertex()
can be used for Bézier curves of all orders. Also, in that proposal, it's no longer necessary to mix commands to create a single primitive (e.g. all Bézier curves can be created using only bezierVertex()
commands and no vertex()
commands, just as Catmull-Rom splines can already be created entirely with curveVertex()
commands); for details, see the third problem in the list of ten problems solved by that proposal. In the same way, the idea here is to be able to create arcs using only arcVertex()
commands.
With the new API for arcVertex()
and the API in #6766, vertex()
works with any number of vertices greater than one, bezierVertex()
works with any number of vertices greater than two, curveVertex()
works with any number of vertices greater than three, and arcVertex()
could actually work with any number of vertices greater than four! When more than five vertices are specifed to arcVertex()
, we could fit the points with an approximating arc. This is an important task in computer vision, so there’s plenty of literature on it. For example, based on a quick glance, this paper may provide a good solution.
Edit: Added the vertex()
case (supports any number of vertices greater than one). Note that with the proposal in #6766, we no longer need to use vertex()
to start a new primitive like a Bézier curve, so I don't think we'd ever call it just once.
For the first question, it seems like we won't really be able to handle both use cases in the same API cleanly, and neither choice would be a regression, so I'm good going with either option.
For the second question, I think I may have missed some details about connected segments of different types, so I've left a comment on the vertex function proposal with more info there. The main concern is that by having variable numbers of function calls per segment, it requires something else to mark the start/end of segments (and I think begin/endContour
in its current form describes something else, so it'd mean having a separate set of markers for separate paths that clip each other with winding order than our markers to separate segments.) I like the idea of a closest-fit, although it has the same issue of requiring markers. Anyway, we can talk about that more on the other PR; as long as we're consistent with arc vertices then we're good!
Thanks @davepagurek! I'll respond to your points and summarize some of the discussion so far.
arcVertex(x, y)
vs. arcVertex(w, h, angle, type, direction, x2, y2)
Either way, it will at least be possible to create elliptical arcs with beginShape()
/endShape()
, so both are an improvement over the current API. I'm currently leaning toward the arcVertex(x, y)
API.
arcVertex(x, y)
Here's an updated list of benefits, including some of the ones I mentioned above:
vertex()
works with any number of vertices greater than one, bezierVertex()
works with any number of vertices greater than two, curveVertex()
works with any number of vertices greater than three, and arcVertex()
could work with any number of vertices greater than four!)arcVertex(x, y)
for the starting vertex and arcVertex(w, h, angle, type, direction, x2, y2)
for the rest of the segment, in order to avoid having to mix vertex types for a single primitive (Problem 3 in #6766). (We could allow mixing just for arc segments, but that would be an inconsistency, since the hope is to eliminate mixing for all the other segment types.)It'd be nice to avoid having to do the extra math for rounded corners, but right now, I'm thinking these other points are pretty compelling. And, at least it'll be possible to make rounded corners with elliptical arcs; in the current version of p5.js, it's not possible at all.
I replied about this point in #6766. Thanks!
Glad you like the idea! Regarding distinct markers for (a) distinguishing primitives (e.g. segments) in composite shapes and (b) distinguishing contours, I think we've found a solution in the discussion on #6766.
Reminder: This p5.js tutorial opens by saying "This short tutorial introduces you to the three types of curves in p5.js: arcs, spline curves, and Bézier curves." In the summary, it specifies that "You can’t make continuous arcs or use them as part of a shape." This is something we'd want to update.
Any progress to push this forward? It would be so useful.
@TheWhoove We currently plan to implement this feature along with larger vertex refactor in p5.js 2.0. What can be helpful would be to work on a potential proof of concept in the dev-2.0
branch so some of the larger details can be worked out before we confirm the implementation.
Increasing Access
This feature increases access in two main ways:
Most appropriate sub-area of p5.js?
Feature request details
Proposal
Add
arcVertex()
to the set of vertex functions in p5, to address a limitation ofbeginShape()
/endShape()
.The problem
Two of p5’s curve-vertex pairs are incomplete. In the table below, asterisks indicate missing items.
line()
vertex()
quadratic()
*quadraticVertex()
bezier()
bezierVertex()
curve()
curveVertex()
arc()
arcVertex()
*Of the missing items,
arcVertex()
is the most important. Arcs are the only type of SVG subpaths not supported by p5’sbeginShape()
/endShape()
. Without them, p5's most powerful shape-making feature cannot make some of the most basic shapes (e.g. lines and Bézier curves can only approximate circles). This leads to complications for both users and developers.The solution
Provide a new function called
arcVertex()
based on SVG’s arc command. Applications include the following:beginShape()
/endShape()
.Specification of
arcVertex()
The following may serve as a draft reference page and usage tutorial.
Description
Used to draw an arc along a circle or ellipse (oval), from one vertex (point) to another. The arc may be drawn along any ellipse, even one that’s been rotated. (If the ellipse is too small to connect the two vertices, its size is automatically adjusted.)
The first vertex is specified with
vertex()
. The second vertex is specified witharcVertex()
. Both are specified withinbeginShape()
andendShape()
, with no parameter passed tobeginShape()
.The arc itself is specified by indicating the shape and rotation of the ellipse (
w
,h
, andangle
), together with its type (MINOR
orMAJOR
) and its direction (COUNTERCLOCKWISE
orCLOCKWISE
).If
DEGREES
is passed toangleMode()
,angle
may be specified in degrees; otherwise,angle
should be specified in radians. IfRADIUS
is passed toellipseMode()
, the shape parameters are interpreted as half of the width and half of the height:arcVertex(20, 50, ... )
results in an arc drawn along an ellipse of width 40 and height 100.Usage
Syntax
Parameters
Tutorial
Related issues
2529
5789
37
3509
4630
844 (suggests a complementary path-to-vertices feature)
458