Description
Increasing access
Implementing this proposal would
- decrease complexity
- increase consistency
- enable new features
As a result, p5.js would be more accessible to beginners, while offering others access to a wider feature set.
Which types of changes would be made?
- Breaking change (Add-on libraries or sketches will work differently even if their code stays the same.)
- Systemic change (Many features or contributor workflows will be affected.)
- Overdue change (Modifications will be made that have been desirable for a long time.)
- Unsure (The community can help to determine the type of change.)
Most appropriate sub-area of p5.js?
- Accessibility
- Color
- Core/Environment/Rendering
- Data
- DOM
- Events
- Image
- IO
- Math
- Typography
- Utilities
- WebGL
- Build process
- Unit testing
- Internationalization
- Friendly errors
- Other (specify if possible)
What's the problem?
Short version
The current API for vertex functions contains complexities, inconsistencies, and hard-to-extend features. It's included below for easier comparison with the proposed API.
// vertex functions (brackets indicate optional parameters)
// copied from the syntax section of the p5.js reference
// note that arcVertex() is planned but not currently included
vertex(x, y, [z], [u], [v])
// arcVertex(x2, y2, x3, y3, x4, y4, x5, y5)
// arcVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4, x5, y5, z5)
quadraticVertex(cx, cy, x3, y3)
quadraticVertex(cx, cy, cz, x3, y3, z3)
bezierVertex(x2, y2, x3, y3, x4, y4)
bezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4)
curveVertex(x, y, [z])
Long version
Eleven distinct problems arise from the current API for vertex functions. Below, each problem is listed and tagged with a problem type. (In case we want to modify the proposed solution, we can check the new version against this list.)
- Texture coordinates: Texture coordinates are currently supported inconsistently, and it's infeasible to fix this without a change to the API. Specifically, the user can specify texture coordinates to
vertex()
but not toquadraticVertex()
,curveVertex()
, orbezierVertex()
, as noted in #5722. Under the current API, specifying texture coordinates would require up to fifteen parameters in a single function call, with the following syntax:bezierVertex(x2, y2, z2, u2, v2, x3, y3, z3, u3, v3, x4, y4, z4, u4, v4)
. [Inconsistency/Inflexibility] - Parameter grouping: The last example may look like it contains a typo, but it doesn't. The current parameter lists for
quadraticVertex()
andbezierVertex()
really do start withx2, y2
/x2, y2, z2
. That's because the first set of parametersx1, y1
/x1, y1, z1
must be specified separately, and we need a way to distinguish the second, third, and fourth points since they're grouped together in the same function call. [Complexity] - Mixed commands: If we want to make a quadratic or cubic Bézier curve, that first point
x1, y1
/x1, y1, z1
gets passed tovertex()
. But that's just for Bézier curves. If we want to make a Catmull-Rom spline, the first point and all successive points get specified directly tocurveVertex()
. In other words, the current API allows the user to callvertex()
to start and continue a polyline, and to callcurveVertex()
to start and continue a Catmull-Rom spline; however, to start a shape with a quadratic or cubic Bézier curve, the user needs to mix commands. [Inconsistency] - Singularity of "vertex": A function like
bezierVertex()
is named after a single vertex, but it accepts coordinates for three points. We may try to reconcile this by identifying the first two of those points as "control points" and the last point as a "vertex," but this is an uncommon distinction, and it doesn't hold up: we cannot reconcile it with the meaning of "vertex" incurveVertex()
. [Inconsistency] - Multiple signatures: With the current API, it's necessary to specify multiple signatures for the same function, which increases complexity. For example, we have both
bezierVertex(x2, y2, x3, y3, x4, y4)
andbezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4)
. Each of these signatures looks rather complicated by itself. [Complexity] - Missing primitives: Right now,
bezierVertex()
andbezier()
are supported, andcurveVertex()
andcurve()
are supported. We also havequadraticVertex()
, butquadratic()
is not supported. (I've proposed separately that we should introduce a new functionarcVertex()
to address a similar inconsistency.) [Inconsistency] - "Quadratic" confusion: The name
quadraticVertex()
is confusing for multiple reasons. BothquadraticVertex()
andbezierVertex()
produce Bézier curves, but only one of them contains the name "Bézier"; in other words, one function is named after the type of curve while the other is named after the order of the curve. Also, many p5 users are students who will have recently learned in algebra that "the vertex of a quadratic" (a parabola) corresponds to its lowest or highest point; that's not generally true of a vertex specified withquadraticVertex()
. [Inconsistency] - Higher-order Bézier curves: With the current API, supporting general higher-order Bézier curves is not possible. (Even if p5.js may not want to implement higher-order Bézier curves, add-on libraries may want to.) [Inflexibility]
- Bézier surfaces: Right now, supporting Bézier surfaces (in p5.js or in an add-on library) would introduce more complications to the API. For example, a quadratic Bézier triangle is defined by six control points. If we specify the first of these with
vertex()
and specify the next four withquadraticVertex()
, we only have five vertices. We could potentially usevertex()
to specify the sixth vertex at the end, but this is inconsistent with howvertex()
is used everywhere else, and it still requires us to mix command types to create a single primitive. [Inflexibility] - Bézier syntax vs. other syntax: The syntax for the vertex functions is inconsistent. The current API bundles multiple points together into a single function call for quadratic and cubic Bézier curves, but it uses one function call per point for polylines and Catmull-Rom splines. [Inconsistency]
- Two meanings of "curve": The name
curveVertex()
uses a general term for a special shape, which leads to inconsistencies. For example, in the p5 reference, “Curves” is used as a general section heading that includes Béziers, whilecurve()
specifically creates Catmull-Rom splines only. This may lead to confusion. For instance, if we introduce a function such ascurveType()
for different spline implementations, it will be hard to guess whether it applies to all curves or only curves made withcurveVertex()
. [Inconsistency]
What's the solution?
The main idea is to pass only one vertex into each vertex function.
Proposed API
The proposed API for p5.js 2.0 is indicated below.1 The splineType()
function may be added in a future release. A sample of spline types has already been discussed, for illustrative purposes.
// mode function (could default to n = 3, with native support for n = 2 and 3, at least)
bezierOrder(n)
// type setter (allows spline types other than Catmull-Rom to be specified, each with their own properties)
// may be added after 2.0
splineType(type)
// property setters (these replace curveTightness() and allow other properties, like interpolated ends)
// plural version accepts an object with key-value pairs
splineProperty(key, value)
splineProperties(options)
// vertex functions (brackets indicate optional parameters)
vertex(x, y, [z], [u], [v])
arcVertex(x, y, [z], [u], [v])
bezierVertex(x, y, [z], [u], [v])
splineVertex(x, y, [z], [u], [v])
Amazingly, this simple API solves all eleven problems listed above 🤯The basic design was inspired by this comment from @davepagurek.
Notes:
bezierOrder()
eliminates the need forquadraticVertex()
arcVertex()
is based on the latest iteration of #6459splineVertex()
replacescurveVertex()
2
Code examples
Example 1: Consistency across all types of curves
Note: Conceptually, polylines can be viewed as linear splines or as chained first-order Béziers.
Polylines (no change proposed) | Splines (proposed) | Bézier curves (proposed) |
---|---|---|
beginShape();
// polyline
// (with three segments)
vertex(x0, y0, z0);
vertex(x1, y1, z1);
vertex(x2, y2, z2);
vertex(x3, y3, z3);
endShape(); |
beginShape();
// Catmull-Rom spline
// (a type of cubic spline)
splineVertex(x0, y0, z0);
splineVertex(x1, y1, z1);
splineVertex(x2, y2, z2);
splineVertex(x3, y3, z3);
endShape(); |
beginShape();
// cubic Bezier curve
bezierOrder(3);
bezierVertex(x0, y0, z0);
bezierVertex(x1, y1, z1);
bezierVertex(x2, y2, z2);
bezierVertex(x3, y3, z3);
endShape(); |
Example 2: Clear function names, readable parameter lists, and flexible design
Note: Chaining quadratic and cubic Bézier curves does not currently work in p5, but issue #6560 aims to address this.
Chained Bézier curves (current) | Chained Bézier curves (proposed) |
---|---|
beginShape();
// two quadratic Bezier curves
// explicit starting vertex required
vertex(x0, y0, z0);
quadraticVertex(x1, y1, z1, x2, y2, z2);
quadraticVertex(x3, y3, z3, x4, y4, z4);
// one cubic Bezier curve
// implicit starting vertex at x4, y4, z4
bezierVertex(x5, y5, z5, x6, y6, z6, x7, y7, z7);
endShape();
|
beginShape();
// two quadratic Bezier curves
// explicit starting vertex required
bezierOrder(2);
bezierVertex(x0, y0, z0);
bezierVertex(x1, y1, z1);
bezierVertex(x2, y2, z2);
bezierVertex(x3, y3, z3);
bezierVertex(x4, y4, z4);
// one cubic Bezier curve
// implicit starting vertex at x4, y4, z4
bezierOrder(3);
bezierVertex(x5, y5, z5);
bezierVertex(x6, y6, z6);
bezierVertex(x7, y7, z7);
endShape(); |
Example 3: Consistency across all chained shapes (curves, triangles, and quads)
Note: Bézier control points are rendered below as a visual aid, but the code for that is not shown.
Pros (updated based on community comments)
- Simplicity: All the complexities noted in the problem statement are eliminated.
- Consistency: All inconsistencies noted in the problem statement are eliminated.
- Flexibility: Multiple new features are possible, with a more intuitive API.
- Readability: Code is easier to read since long lists of positional parameters are eliminated.
- Predictability: An arc starts with
arcVertex()
, rather thanvertex()
. Same for other curve types.
Cons (updated based on community comments)
- Breaking changes: Breaking changes will always cause difficulties when they are first introduced, for some people. For example, YouTubers may need to update their video tutorials, and add-on library authors may need to update their code if they want to use the most recent version of p5.js. However, the proposal could be implemented without breaking changes, at a small cost.3 Another option is to support the 1.x API via a compatibility add-on. Based on further discussion, we've decided to use a compatibility add-on.
- Differences with other APIs: Changing the p5.js API would mean a bigger departure from the Processing API, the native canvas API, and the SVG API. The latter two API's are similar to p5's, but they sidestep some of the problems by referring to their commands as "curve" commands rather than "vertex" commands. In the context of p5, beginners won't tend to know those other APIs, and more experienced users may have less trouble adapting, so this seems like a smaller concern.
Proposal status
Under review
Updates
This proposal has been updated to reflect some small adjustments, based on the discussion in the comments section:
bezierOrder()
was added so that chained segments of different orders can be distinguishedarcVertex()
was added based on the current consensus in #6459curveVertex()
was replaced withsplineVertex()
based on Problem 11, which was added to the problem listsplineProperty()
was added as a more extensible way to set spline properties (this replacescurveTightness()
)splineProperties()
was added based on a discussion starting with this commentsplineType(type)
was added as a potential future addition (in a later version of p5.js)
Additional code examples have also been included.
Footnotes
-
We may also want to support texture coordinates when a
z
-coordinate is not passed. This is currently supported byvertex()
(see the last example fortexture()
in the reference); however, it requires a separate signature forvertex()
that is not documented on its reference page (i.e.vertex(x, y, [u], [v])
). We'd need to discuss whether the benefits justify the extra complexity (namely, documenting and supporting an extra signature for every type of vertex function). ↩ -
For consistency, we could also rename
curve()
asspline()
. ↩ -
We can distinguish new and old usage of
bezierVertex()
by detecting the number of arguments, and we can deprecate the old usage. We could deprecatequadraticVertex()
andcurveVertex()
entirely with the @deprecated tag in the inline documentation. ↩
Metadata
Metadata
Assignees
Type
Projects
Status