-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add ShapePrimitive support for arcs and ellipses #8617
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
Changes from 1 commit
01aa360
bea47ea
e8cd4fd
20b191f
7d9298c
59400f6
db7cd99
71aaf21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,7 @@ import { MediaElement } from '../dom/p5.MediaElement'; | |||||||||||||||||||||||
| import { RGBHDR } from '../color/creating_reading'; | ||||||||||||||||||||||||
| import FilterRenderer2D from '../image/filterRenderer2D'; | ||||||||||||||||||||||||
| import { Matrix } from '../math/p5.Matrix'; | ||||||||||||||||||||||||
| import { PrimitiveToPath2DConverter } from '../shape/custom_shapes'; | ||||||||||||||||||||||||
| import { PrimitiveToPath2DConverter, ArcPrimitive, EllipsePrimitive } from '../shape/custom_shapes'; | ||||||||||||||||||||||||
| import { DefaultFill, textCoreConstants } from '../type/textCore'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -661,13 +661,6 @@ class Renderer2D extends Renderer { | |||||||||||||||||||||||
| * start <= stop < start + TWO_PI | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
| arc(x, y, w, h, start, stop, mode) { | ||||||||||||||||||||||||
| const ctx = this.drawingContext; | ||||||||||||||||||||||||
| const rx = w / 2.0; | ||||||||||||||||||||||||
| const ry = h / 2.0; | ||||||||||||||||||||||||
| const epsilon = 0.00001; // Smallest visible angle on displays up to 4K. | ||||||||||||||||||||||||
| let arcToDraw = 0; | ||||||||||||||||||||||||
| const curves = []; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const centerX = x + w / 2, | ||||||||||||||||||||||||
| centerY = y + h / 2, | ||||||||||||||||||||||||
| radiusX = w / 2, | ||||||||||||||||||||||||
|
|
@@ -681,48 +674,16 @@ class Renderer2D extends Renderer { | |||||||||||||||||||||||
| this.clipPath.addPath(tempPath, relativeTransform); | ||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| // Determines whether to add a line to the center, which should be done | ||||||||||||||||||||||||
| // when the mode is PIE or default; as well as when the start and end | ||||||||||||||||||||||||
| // angles do not form a full circle. | ||||||||||||||||||||||||
| const createPieSlice = ! ( | ||||||||||||||||||||||||
| mode === constants.CHORD || | ||||||||||||||||||||||||
| mode === constants.OPEN || | ||||||||||||||||||||||||
| (stop - start) % constants.TWO_PI === 0 | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Fill curves | ||||||||||||||||||||||||
| if (this.states.fillColor) { | ||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||
| ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop); | ||||||||||||||||||||||||
| if (createPieSlice) ctx.lineTo(centerX, centerY); | ||||||||||||||||||||||||
| ctx.closePath(); | ||||||||||||||||||||||||
| ctx.fill(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Stroke curves | ||||||||||||||||||||||||
| if (this.states.strokeColor) { | ||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||
| ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (mode === constants.PIE && createPieSlice) { | ||||||||||||||||||||||||
| // In PIE mode, stroke is added to the center and back to path, | ||||||||||||||||||||||||
| // unless the pie forms a complete ellipse (see: createPieSlice) | ||||||||||||||||||||||||
| ctx.lineTo(centerX, centerY); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (mode === constants.PIE || mode === constants.CHORD) { | ||||||||||||||||||||||||
| // Stroke connects back to path begin for both PIE and CHORD | ||||||||||||||||||||||||
| ctx.closePath(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| ctx.stroke(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| const primitive = new ArcPrimitive(x, y, w, h, start, stop, mode); | ||||||||||||||||||||||||
| const shape = { accept(visitor) { primitive.accept(visitor); } }; | ||||||||||||||||||||||||
| this.drawShape(shape); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ellipse(args) { | ||||||||||||||||||||||||
| const ctx = this.drawingContext; | ||||||||||||||||||||||||
| const doFill = !!this.states.fillColor, | ||||||||||||||||||||||||
| doStroke = this.states.strokeColor; | ||||||||||||||||||||||||
| const x = parseFloat(args[0]), | ||||||||||||||||||||||||
|
|
@@ -751,15 +712,10 @@ class Renderer2D extends Renderer { | |||||||||||||||||||||||
| this.clipPath.addPath(tempPath, relativeTransform); | ||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||
| ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI); | ||||||||||||||||||||||||
| ctx.closePath(); | ||||||||||||||||||||||||
| if (doFill) { | ||||||||||||||||||||||||
| ctx.fill(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (doStroke) { | ||||||||||||||||||||||||
| ctx.stroke(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const primitive = new EllipsePrimitive(x, y, w, h); | ||||||||||||||||||||||||
| const shape = { accept(visitor) { primitive.accept(visitor); } }; | ||||||||||||||||||||||||
| this.drawShape(shape); | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like p5.js/src/core/p5.Renderer2D.js Lines 269 to 279 in 01aa360
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -466,6 +466,77 @@ class Quad extends ShapePrimitive { | |||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| class ArcPrimitive extends ShapePrimitive { | ||||||||||||||||||||||||||||||||||||||||||||||||
| #x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #y; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #w; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #h; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #start; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #stop; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #mode; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // vertexCapacity 0 means this primitive should not accumulate normal path vertices | ||||||||||||||||||||||||||||||||||||||||||||||||
| #vertexCapacity = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(x, y, w, h, start, stop, mode) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // ShapePrimitive requires at least one vertex; pass a placeholder | ||||||||||||||||||||||||||||||||||||||||||||||||
| super(new Vertex({ position: new Vector(x + w / 2, y + h / 2) })); | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#x = x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#y = y; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#w = w; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#h = h; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#start = start; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#stop = stop; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#mode = mode; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| get x() { return this.#x; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get y() { return this.#y; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get w() { return this.#w; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get h() { return this.#h; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get start() { return this.#start; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get stop() { return this.#stop; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get mode() { return this.#mode; } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| get vertexCapacity() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return this.#vertexCapacity; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| accept(visitor) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitor.visitArcPrimitive(this); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| class EllipsePrimitive extends ShapePrimitive { | ||||||||||||||||||||||||||||||||||||||||||||||||
| #x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #y; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #w; | ||||||||||||||||||||||||||||||||||||||||||||||||
| #h; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // vertexCapacity 0 means this primitive should not accumulate normal path vertices | ||||||||||||||||||||||||||||||||||||||||||||||||
| #vertexCapacity = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(x, y, w, h) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| super(new Vertex({ position: new Vector(x + w / 2, y + h / 2) })); | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#x = x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#y = y; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#w = w; | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.#h = h; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| get x() { return this.#x; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get y() { return this.#y; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get w() { return this.#w; } | ||||||||||||||||||||||||||||||||||||||||||||||||
| get h() { return this.#h; } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| get vertexCapacity() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return this.#vertexCapacity; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| accept(visitor) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitor.visitEllipsePrimitive(this); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // ---- TESSELLATION PRIMITIVES ---- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| class TriangleFan extends ShapePrimitive { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1003,6 +1074,12 @@ class PrimitiveVisitor { | |||||||||||||||||||||||||||||||||||||||||||||||
| visitArcSegment(arcSegment) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Method visitArcSegment() has not been implemented.'); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitArcPrimitive(arc) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Method visitArcPrimitive() has not been implemented.'); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitEllipsePrimitive(ellipse) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Method visitEllipsePrimitive() has not been implemented.'); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // isolated primitives | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitPoint(point) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1151,6 +1228,34 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { | |||||||||||||||||||||||||||||||||||||||||||||||
| this.path.closePath(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitArcPrimitive(arc) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerX = arc.x + arc.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerY = arc.y + arc.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusX = arc.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusY = arc.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| this.path.ellipse( | ||||||||||||||||||||||||||||||||||||||||||||||||
| centerX, centerY, radiusX, radiusY, 0, arc.start, arc.stop | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (arc.mode === constants.OPEN) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // OPEN: leave path open — arc stroke/fill is just the curve | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (arc.mode === constants.CHORD) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| this.path.closePath(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.path.lineTo(centerX, centerY); | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.path.closePath(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitEllipsePrimitive(ellipse) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerX = ellipse.x + ellipse.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerY = ellipse.y + ellipse.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusX = ellipse.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusY = ellipse.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| this.path.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitQuadStrip(quadStrip) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < quadStrip.vertices.length - 3; i += 2) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const v0 = quadStrip.vertices[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1277,6 +1382,50 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { | |||||||||||||||||||||||||||||||||||||||||||||||
| // WebGL itself interprets the vertices as a strip, no reformatting needed | ||||||||||||||||||||||||||||||||||||||||||||||||
| this.contours.push(quadStrip.vertices.slice()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| visitArcPrimitive(arc) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerX = arc.x + arc.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const centerY = arc.y + arc.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusX = arc.w / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const radiusY = arc.h / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const numPoints = Math.max(3, this.curveDetail); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const verts = []; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (arc.mode === constants.PIE) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| verts.push(new Vertex({ position: new Vector(centerX, centerY) })); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i <= numPoints; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const angle = arc.start + (arc.stop - arc.start) * (i / numPoints); | ||||||||||||||||||||||||||||||||||||||||||||||||
| verts.push(new Vertex({ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| vertex(position, textureCoordinates, { isClosing = false } = {}) { | |
| const added = this.#generalVertex('vertex', position, textureCoordinates); | |
| added.isClosing = isClosing; | |
| } | |
| bezierVertex(position, textureCoordinates) { | |
| this.#generalVertex('bezierVertex', position, textureCoordinates); | |
| } | |
| splineVertex(position, textureCoordinates) { | |
| this.#generalVertex('splineVertex', position, textureCoordinates); | |
| } | |
| arcVertex(position, textureCoordinates) { | |
| this.#generalVertex('arcVertex', position, textureCoordinates); | |
| } | |
| beginContour(shapeKind = constants.PATH) { | |
| if (this.at(-1)?.kind === constants.EMPTY_PATH) { | |
| this.contours.pop(); | |
| } | |
| this.contours.push(new Contour(shapeKind)); | |
| } |
That's generally to support a construct in beginShape/endShape where you can call something in the form of somethingVertex(x, y, [z], [u], [v]), letting you specify texture coordinates between points, and call fill() or stroke() between vertices too. There's a proposal #6459 on what arcs would look like in a per-vertex form like that, and arcTo in the canvas 2D API works like that too.
We don't need to actually implement arcVertex yet, but ideally we'd start to set ourselves up to be able to do that eventually. That likely means having an arc segment with #vertexCapacity of 2, so you can put a vertex at the start and a vertex at the end, referring to these:

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arguably the arcTo primitive could be separate from the arc primitive, but since a lot will be shared, I think the idea would be ideally to build it in a way where arc is made out of arcTo-style primitives. But there's a case to be made that we only get that benefit once we have arcTo, and having any support for arc unblocks other features like SVG export. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, @davepagurek ! That makes sense about the consistency with other curves.
For this PR, I'd like to propose taking the current approach for now using a standalone ArcPrimitive to keep the precise geometry for high-quality SVG export.
However, I will add a method to the class that acts as a placeholder for future decomposition. That way, we can implement arcTo and arcVertex() later without breaking the standard API. whats your thoughts in this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than making an object that isn't a full
p5.Shape, could we instead make a new realp5.Shapeand create a method on it that adds an ellipse primitive? similar to the relation between these methods and their corresponding primitives:p5.js/src/shape/custom_shapes.js
Lines 891 to 906 in 618e4c8