Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 8 additions & 52 deletions src/core/p5.Renderer2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';


Expand Down Expand Up @@ -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,
Expand All @@ -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]),
Expand Down Expand Up @@ -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);

return this;
}
Expand Down
151 changes: 151 additions & 0 deletions src/shape/custom_shapes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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({
position: new Vector(
centerX + radiusX * Math.cos(angle),
centerY + radiusY * Math.sin(angle)
)
}));
}

this.contours.push(verts);
}
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;
const numPoints = Math.max(3, this.curveDetail);
const verts = [];

for (let i = 0; i <= numPoints; i++) {
const angle = (2 * Math.PI * i) / numPoints;
verts.push(new Vertex({
position: new Vector(
centerX + radiusX * Math.cos(angle),
centerY + radiusY * Math.sin(angle)
)
}));
}

this.contours.push(verts);
}
}

class PointAtLengthGetter extends PrimitiveVisitor {
Expand Down Expand Up @@ -2793,6 +2942,8 @@ export {
Line,
Triangle,
Quad,
ArcPrimitive,
EllipsePrimitive,
TriangleFan,
TriangleStrip,
QuadStrip,
Expand Down