Skip to content

Commit 01aa360

Browse files
committed
Add ShapePrimitive support for arcs and ellipses
1 parent 3b40210 commit 01aa360

2 files changed

Lines changed: 159 additions & 52 deletions

File tree

src/core/p5.Renderer2D.js

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MediaElement } from '../dom/p5.MediaElement';
88
import { RGBHDR } from '../color/creating_reading';
99
import FilterRenderer2D from '../image/filterRenderer2D';
1010
import { Matrix } from '../math/p5.Matrix';
11-
import { PrimitiveToPath2DConverter } from '../shape/custom_shapes';
11+
import { PrimitiveToPath2DConverter, ArcPrimitive, EllipsePrimitive } from '../shape/custom_shapes';
1212
import { DefaultFill, textCoreConstants } from '../type/textCore';
1313

1414

@@ -661,13 +661,6 @@ class Renderer2D extends Renderer {
661661
* start <= stop < start + TWO_PI
662662
*/
663663
arc(x, y, w, h, start, stop, mode) {
664-
const ctx = this.drawingContext;
665-
const rx = w / 2.0;
666-
const ry = h / 2.0;
667-
const epsilon = 0.00001; // Smallest visible angle on displays up to 4K.
668-
let arcToDraw = 0;
669-
const curves = [];
670-
671664
const centerX = x + w / 2,
672665
centerY = y + h / 2,
673666
radiusX = w / 2,
@@ -681,48 +674,16 @@ class Renderer2D extends Renderer {
681674
this.clipPath.addPath(tempPath, relativeTransform);
682675
return this;
683676
}
684-
// Determines whether to add a line to the center, which should be done
685-
// when the mode is PIE or default; as well as when the start and end
686-
// angles do not form a full circle.
687-
const createPieSlice = ! (
688-
mode === constants.CHORD ||
689-
mode === constants.OPEN ||
690-
(stop - start) % constants.TWO_PI === 0
691-
);
692-
693-
// Fill curves
694-
if (this.states.fillColor) {
695-
ctx.beginPath();
696-
ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop);
697-
if (createPieSlice) ctx.lineTo(centerX, centerY);
698-
ctx.closePath();
699-
ctx.fill();
700-
}
701677

702-
// Stroke curves
703-
if (this.states.strokeColor) {
704-
ctx.beginPath();
705-
ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop);
706-
707-
if (mode === constants.PIE && createPieSlice) {
708-
// In PIE mode, stroke is added to the center and back to path,
709-
// unless the pie forms a complete ellipse (see: createPieSlice)
710-
ctx.lineTo(centerX, centerY);
711-
}
712-
713-
if (mode === constants.PIE || mode === constants.CHORD) {
714-
// Stroke connects back to path begin for both PIE and CHORD
715-
ctx.closePath();
716-
}
717-
ctx.stroke();
718-
}
678+
const primitive = new ArcPrimitive(x, y, w, h, start, stop, mode);
679+
const shape = { accept(visitor) { primitive.accept(visitor); } };
680+
this.drawShape(shape);
719681

720682
return this;
721683

722684
}
723685

724686
ellipse(args) {
725-
const ctx = this.drawingContext;
726687
const doFill = !!this.states.fillColor,
727688
doStroke = this.states.strokeColor;
728689
const x = parseFloat(args[0]),
@@ -751,15 +712,10 @@ class Renderer2D extends Renderer {
751712
this.clipPath.addPath(tempPath, relativeTransform);
752713
return this;
753714
}
754-
ctx.beginPath();
755-
ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI);
756-
ctx.closePath();
757-
if (doFill) {
758-
ctx.fill();
759-
}
760-
if (doStroke) {
761-
ctx.stroke();
762-
}
715+
716+
const primitive = new EllipsePrimitive(x, y, w, h);
717+
const shape = { accept(visitor) { primitive.accept(visitor); } };
718+
this.drawShape(shape);
763719

764720
return this;
765721
}

src/shape/custom_shapes.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,77 @@ class Quad extends ShapePrimitive {
466466
}
467467
}
468468

469+
class ArcPrimitive extends ShapePrimitive {
470+
#x;
471+
#y;
472+
#w;
473+
#h;
474+
#start;
475+
#stop;
476+
#mode;
477+
// vertexCapacity 0 means this primitive should not accumulate normal path vertices
478+
#vertexCapacity = 0;
479+
480+
constructor(x, y, w, h, start, stop, mode) {
481+
// ShapePrimitive requires at least one vertex; pass a placeholder
482+
super(new Vertex({ position: new Vector(x + w / 2, y + h / 2) }));
483+
this.#x = x;
484+
this.#y = y;
485+
this.#w = w;
486+
this.#h = h;
487+
this.#start = start;
488+
this.#stop = stop;
489+
this.#mode = mode;
490+
}
491+
492+
get x() { return this.#x; }
493+
get y() { return this.#y; }
494+
get w() { return this.#w; }
495+
get h() { return this.#h; }
496+
get start() { return this.#start; }
497+
get stop() { return this.#stop; }
498+
get mode() { return this.#mode; }
499+
500+
get vertexCapacity() {
501+
return this.#vertexCapacity;
502+
}
503+
504+
accept(visitor) {
505+
visitor.visitArcPrimitive(this);
506+
}
507+
}
508+
509+
class EllipsePrimitive extends ShapePrimitive {
510+
#x;
511+
#y;
512+
#w;
513+
#h;
514+
// vertexCapacity 0 means this primitive should not accumulate normal path vertices
515+
#vertexCapacity = 0;
516+
517+
constructor(x, y, w, h) {
518+
519+
super(new Vertex({ position: new Vector(x + w / 2, y + h / 2) }));
520+
this.#x = x;
521+
this.#y = y;
522+
this.#w = w;
523+
this.#h = h;
524+
}
525+
526+
get x() { return this.#x; }
527+
get y() { return this.#y; }
528+
get w() { return this.#w; }
529+
get h() { return this.#h; }
530+
531+
get vertexCapacity() {
532+
return this.#vertexCapacity;
533+
}
534+
535+
accept(visitor) {
536+
visitor.visitEllipsePrimitive(this);
537+
}
538+
}
539+
469540
// ---- TESSELLATION PRIMITIVES ----
470541

471542
class TriangleFan extends ShapePrimitive {
@@ -1003,6 +1074,12 @@ class PrimitiveVisitor {
10031074
visitArcSegment(arcSegment) {
10041075
throw new Error('Method visitArcSegment() has not been implemented.');
10051076
}
1077+
visitArcPrimitive(arc) {
1078+
throw new Error('Method visitArcPrimitive() has not been implemented.');
1079+
}
1080+
visitEllipsePrimitive(ellipse) {
1081+
throw new Error('Method visitEllipsePrimitive() has not been implemented.');
1082+
}
10061083

10071084
// isolated primitives
10081085
visitPoint(point) {
@@ -1151,6 +1228,34 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor {
11511228
this.path.closePath();
11521229
}
11531230
}
1231+
visitArcPrimitive(arc) {
1232+
const centerX = arc.x + arc.w / 2;
1233+
const centerY = arc.y + arc.h / 2;
1234+
const radiusX = arc.w / 2;
1235+
const radiusY = arc.h / 2;
1236+
1237+
this.path.ellipse(
1238+
centerX, centerY, radiusX, radiusY, 0, arc.start, arc.stop
1239+
);
1240+
1241+
if (arc.mode === constants.OPEN) {
1242+
// OPEN: leave path open — arc stroke/fill is just the curve
1243+
} else if (arc.mode === constants.CHORD) {
1244+
1245+
this.path.closePath();
1246+
} else {
1247+
this.path.lineTo(centerX, centerY);
1248+
this.path.closePath();
1249+
}
1250+
}
1251+
visitEllipsePrimitive(ellipse) {
1252+
const centerX = ellipse.x + ellipse.w / 2;
1253+
const centerY = ellipse.y + ellipse.h / 2;
1254+
const radiusX = ellipse.w / 2;
1255+
const radiusY = ellipse.h / 2;
1256+
1257+
this.path.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI);
1258+
}
11541259
visitQuadStrip(quadStrip) {
11551260
for (let i = 0; i < quadStrip.vertices.length - 3; i += 2) {
11561261
const v0 = quadStrip.vertices[i];
@@ -1277,6 +1382,50 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor {
12771382
// WebGL itself interprets the vertices as a strip, no reformatting needed
12781383
this.contours.push(quadStrip.vertices.slice());
12791384
}
1385+
visitArcPrimitive(arc) {
1386+
const centerX = arc.x + arc.w / 2;
1387+
const centerY = arc.y + arc.h / 2;
1388+
const radiusX = arc.w / 2;
1389+
const radiusY = arc.h / 2;
1390+
const numPoints = Math.max(3, this.curveDetail);
1391+
const verts = [];
1392+
1393+
if (arc.mode === constants.PIE) {
1394+
verts.push(new Vertex({ position: new Vector(centerX, centerY) }));
1395+
}
1396+
1397+
for (let i = 0; i <= numPoints; i++) {
1398+
const angle = arc.start + (arc.stop - arc.start) * (i / numPoints);
1399+
verts.push(new Vertex({
1400+
position: new Vector(
1401+
centerX + radiusX * Math.cos(angle),
1402+
centerY + radiusY * Math.sin(angle)
1403+
)
1404+
}));
1405+
}
1406+
1407+
this.contours.push(verts);
1408+
}
1409+
visitEllipsePrimitive(ellipse) {
1410+
const centerX = ellipse.x + ellipse.w / 2;
1411+
const centerY = ellipse.y + ellipse.h / 2;
1412+
const radiusX = ellipse.w / 2;
1413+
const radiusY = ellipse.h / 2;
1414+
const numPoints = Math.max(3, this.curveDetail);
1415+
const verts = [];
1416+
1417+
for (let i = 0; i <= numPoints; i++) {
1418+
const angle = (2 * Math.PI * i) / numPoints;
1419+
verts.push(new Vertex({
1420+
position: new Vector(
1421+
centerX + radiusX * Math.cos(angle),
1422+
centerY + radiusY * Math.sin(angle)
1423+
)
1424+
}));
1425+
}
1426+
1427+
this.contours.push(verts);
1428+
}
12801429
}
12811430

12821431
class PointAtLengthGetter extends PrimitiveVisitor {
@@ -2793,6 +2942,8 @@ export {
27932942
Line,
27942943
Triangle,
27952944
Quad,
2945+
ArcPrimitive,
2946+
EllipsePrimitive,
27962947
TriangleFan,
27972948
TriangleStrip,
27982949
QuadStrip,

0 commit comments

Comments
 (0)