diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 2a9690fcdf..7d091c9d1d 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -867,266 +867,33 @@ class Renderer2D extends p5.Renderer { endShape( mode, - vertices, isCurve, isBezier, isQuadratic, isContour, - shapeKind + shapeKind, + allVertsObj, + currentContour, + closeShape ) { - if (vertices.length === 0) { - return this; - } - if (!this._doStroke && !this._doFill) { - return this; - } - const closeShape = mode === constants.CLOSE; - let v; - if (closeShape && !isContour) { - vertices.push(vertices[0]); - } - let i, j; - const numVerts = vertices.length; - if (isCurve && shapeKind === null) { - if (numVerts > 3) { - const b = [], - s = 1 - this._curveTightness; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[1][0], vertices[1][1]); - for (i = 1; i + 2 < numVerts; i++) { - v = vertices[i]; - b[0] = [v[0], v[1]]; - b[1] = [ - v[0] + (s * vertices[i + 1][0] - s * vertices[i - 1][0]) / 6, - v[1] + (s * vertices[i + 1][1] - s * vertices[i - 1][1]) / 6 - ]; - b[2] = [ - vertices[i + 1][0] + - (s * vertices[i][0] - s * vertices[i + 2][0]) / 6, - vertices[i + 1][1] + - (s * vertices[i][1] - s * vertices[i + 2][1]) / 6 - ]; - b[3] = [vertices[i + 1][0], vertices[i + 1][1]]; - this.drawingContext.bezierCurveTo( - b[1][0], - b[1][1], - b[2][0], - b[2][1], - b[3][0], - b[3][1] - ); - } - if (closeShape) { - this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); - } - this._doFillStrokeClose(closeShape); - } - } else if ( - isBezier && - shapeKind === null - ) { - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 0; i < numVerts; i++) { - if (vertices[i].isVert) { - if (vertices[i].moveTo) { - this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); - } else { - this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); - } - } else { - this.drawingContext.bezierCurveTo( - vertices[i][0], - vertices[i][1], - vertices[i][2], - vertices[i][3], - vertices[i][4], - vertices[i][5] - ); - } - } - this._doFillStrokeClose(closeShape); - } else if ( - isQuadratic && - shapeKind === null - ) { - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 0; i < numVerts; i++) { - if (vertices[i].isVert) { - if (vertices[i].moveTo) { - this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); - } else { - this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); - } - } else { - this.drawingContext.quadraticCurveTo( - vertices[i][0], - vertices[i][1], - vertices[i][2], - vertices[i][3] - ); - } - } - this._doFillStrokeClose(closeShape); - } else { - if (shapeKind === constants.POINTS) { - for (i = 0; i < numVerts; i++) { - v = vertices[i]; - if (this._doStroke) { - this._pInst.stroke(v[6]); - } - this._pInst.point(v[0], v[1]); - } - } else if (shapeKind === constants.LINES) { - for (i = 0; i + 1 < numVerts; i += 2) { - v = vertices[i]; - if (this._doStroke) { - this._pInst.stroke(vertices[i + 1][6]); - } - this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]); - } - } else if (shapeKind === constants.TRIANGLES) { - for (i = 0; i + 2 < numVerts; i += 3) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(v[0], v[1]); - this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); - this.drawingContext.closePath(); - if (!this._clipping && this._doFill) { - this._pInst.fill(vertices[i + 2][5]); - this.drawingContext.fill(); - } - if (!this._clipping && this._doStroke) { - this._pInst.stroke(vertices[i + 2][6]); - this.drawingContext.stroke(); - } - } - } else if (shapeKind === constants.TRIANGLE_STRIP) { - for (i = 0; i + 1 < numVerts; i++) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this._doStroke) { - this._pInst.stroke(vertices[i + 1][6]); - } - if (!this._clipping && this._doFill) { - this._pInst.fill(vertices[i + 1][5]); - } - if (i + 2 < numVerts) { - this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); - if (!this._clipping && this._doStroke) { - this._pInst.stroke(vertices[i + 2][6]); - } - if (!this._clipping && this._doFill) { - this._pInst.fill(vertices[i + 2][5]); - } - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.TRIANGLE_FAN) { - if (numVerts > 2) { - // For performance reasons, try to batch as many of the - // fill and stroke calls as possible. - if (!this._clipping) this.drawingContext.beginPath(); - for (i = 2; i < numVerts; i++) { - v = vertices[i]; - this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); - this.drawingContext.lineTo(vertices[i - 1][0], vertices[i - 1][1]); - this.drawingContext.lineTo(v[0], v[1]); - this.drawingContext.lineTo(vertices[0][0], vertices[0][1]); - // If the next colour is going to be different, stroke / fill now - if (i < numVerts - 1) { - if ( - (this._doFill && v[5] !== vertices[i + 1][5]) || - (this._doStroke && v[6] !== vertices[i + 1][6]) - ) { - if (!this._clipping && this._doFill) { - this._pInst.fill(v[5]); - this.drawingContext.fill(); - this._pInst.fill(vertices[i + 1][5]); - } - if (!this._clipping && this._doStroke) { - this._pInst.stroke(v[6]); - this.drawingContext.stroke(); - this._pInst.stroke(vertices[i + 1][6]); - } - this.drawingContext.closePath(); - if (!this._clipping) this.drawingContext.beginPath(); // Begin the next one - } - } - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.QUADS) { - for (i = 0; i + 3 < numVerts; i += 4) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(v[0], v[1]); - for (j = 1; j < 4; j++) { - this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]); - } - this.drawingContext.lineTo(v[0], v[1]); - if (!this._clipping && this._doFill) { - this._pInst.fill(vertices[i + 3][5]); - } - if (!this._clipping && this._doStroke) { - this._pInst.stroke(vertices[i + 3][6]); - } - this._doFillStrokeClose(closeShape); - } - } else if (shapeKind === constants.QUAD_STRIP) { - if (numVerts > 3) { - for (i = 0; i + 1 < numVerts; i += 2) { - v = vertices[i]; - if (!this._clipping) this.drawingContext.beginPath(); - if (i + 3 < numVerts) { - this.drawingContext.moveTo( - vertices[i + 2][0], vertices[i + 2][1]); - this.drawingContext.lineTo(v[0], v[1]); - this.drawingContext.lineTo( - vertices[i + 1][0], vertices[i + 1][1]); - this.drawingContext.lineTo( - vertices[i + 3][0], vertices[i + 3][1]); - if (!this._clipping && this._doFill) { - this._pInst.fill(vertices[i + 3][5]); - } - if (!this._clipping && this._doStroke) { - this._pInst.stroke(vertices[i + 3][6]); - } - } else { - this.drawingContext.moveTo(v[0], v[1]); - this.drawingContext.lineTo( - vertices[i + 1][0], vertices[i + 1][1]); - } - this._doFillStrokeClose(closeShape); - } - } - } else { - if (!this._clipping) this.drawingContext.beginPath(); - this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); - for (i = 1; i < numVerts; i++) { - v = vertices[i]; - if (v.isVert) { - if (v.moveTo) { - if (closeShape) this.drawingContext.closePath(); - this.drawingContext.moveTo(v[0], v[1]); - } else { - this.drawingContext.lineTo(v[0], v[1]); - } - } - } - this._doFillStrokeClose(closeShape); - } + let initialVertex = allVertsObj[0]; + allVertsObj.currentCoordinates = initialVertex.coordinates; + this.drawingContext.moveTo(...allVertsObj.currentCoordinates); + + for (const vert of allVertsObj) { + vert.addToCanvasPath(this.drawingContext); + allVertsObj.currentCoordinates = vert.coordinates; } + this._doFillStrokeClose(closeShape); + isCurve = false; isBezier = false; isQuadratic = false; isContour = false; + if (closeShape) { - vertices.pop(); + currentContour.segmentss.pop(); } - return this; } ////////////////////////////////////////////// diff --git a/src/core/shape/shape.js b/src/core/shape/shape.js new file mode 100644 index 0000000000..a0bb3ef66b --- /dev/null +++ b/src/core/shape/shape.js @@ -0,0 +1,158 @@ +export class Shape { + constructor() { + this.contours = []; + } + + addContour(c) { + this.contours.push(c); + } + + get contourss() { + return this.contours; + } +} + +export class Contour { + constructor(firstVertex = null) { + this.firstVertex = firstVertex; + //contourVertices/Segments + this.segments = []; + } + + get segmentss() { + return this.segments; + } + + get firstSegment() { + return this.segments[0]; + } + + addSegment(segment) { + this.segments.push(segment); + } +} + +export class ContourSegment { + constructor() { + // this.index = index; + } + + getStartVertex() { + // logic to retrieve start vertex + } + + getEndVertex() { + // logic to retrieve end vertex + } + + accept(visitor) { + // logic to accept a visitor + } +} + +export class ContourSegment2D extends ContourSegment { + constructor() { + super(); + this.vertices = []; + } + addVertex(vertex) { + this.vertices.push(vertex); + } + get verticess() { + return this.vertices; + } +} + +export class Vertex { + constructor(vert) { + this.data = vert; + } + + get coordinates() { + return [this.data[0], this.data[1]]; + } + + addToCanvasPath(drawingContext) { + drawingContext.lineTo(this.data[0], this.data[1]); + } +} + + +export class BezierVertex { + constructor(vert) { + this.data = vert; + } + + get coordinates() { + return this.data.slice(-2); + } + + addToCanvasPath(drawingContext) { + drawingContext.bezierCurveTo(...this.data); + } +} + +export class QuadraticVertex { + constructor(vert) { + this.data = vert; + } + + get coordinates() { + return this.data.slice(-2); + } + + addToCanvasPath(drawingContext) { + drawingContext.quadraticCurveTo(...this.data); + } +} + + +export class CurveVertex { + constructor(x, y) { + this.data = [x, y]; + this.type = 'curveVertex'; + } + + get coordinates() { + let isComplete = this.data.length >= 8; + let secondToLastPoint = this.data.slice(-4, -2); + + if(isComplete) { + return secondToLastPoint; + } + else { + return null; + } + } + + addToCanvasPath(drawingContext) { + let bezierArrays = catmullRomToBezier(this.data, 0); + for (const arr of bezierArrays) { + drawingContext.bezierCurveTo(...arr); + } + } +} + +function catmullRomToBezier(vertices, tightness) { + let X0, Y0, X1, Y1, X2, Y2, X3, Y3; + let s = 1 - tightness; + let bezX1, bezY1, bezX2, bezY2, bezX3, bezY3; + let bezArrays = []; + + for (let i = 2; i + 4 < vertices.length; i+= 2) { + [X0, Y0, X1, Y1, X2, Y2, X3, Y3] = vertices.slice(i - 2, i + 6); + + bezX1 = X1 + (s * X2 - s * X0) / 6; + bezY1 = Y1 + (s * Y2 - s * Y0) / 6; + + bezX2 = X2 + (s * X1 - s * X3) / 6; + bezY2 = Y2 + (s * Y1 - s * Y3) / 6; + + bezX3 = X2; + bezY3 = Y2; + + bezArrays.push([bezX1, bezY1, bezX2, bezY2, bezX3, bezY3]); + } + return bezArrays; +} + diff --git a/src/core/shape/vertex.js b/src/core/shape/vertex.js index b08d5830cf..f5df141920 100644 --- a/src/core/shape/vertex.js +++ b/src/core/shape/vertex.js @@ -8,15 +8,21 @@ import p5 from '../main'; import * as constants from '../constants'; +import { + ContourSegment2D, + Shape, + Vertex, + Contour, + BezierVertex, QuadraticVertex, CurveVertex +} from './shape'; + +let shape = null; let shapeKind = null; -let vertices = []; -let contourVertices = []; let isBezier = false; let isCurve = false; let isQuadratic = false; let isContour = false; let isFirstContour = true; - /** * Use the beginContour() and * endContour() functions to create negative shapes @@ -58,11 +64,11 @@ let isFirstContour = true; * @alt * white rect and smaller grey rect with red outlines in center of canvas. */ -p5.prototype.beginContour = function() { +p5.prototype.beginContour = function () { if (this._renderer.isP3D) { this._renderer.beginContour(); } else { - contourVertices = []; + shape.addContour(new Contour()); isContour = true; } return this; @@ -269,7 +275,7 @@ p5.prototype.beginContour = function() { * 3 side-by-side white rectangles center rect is smaller in mid-right canvas. * Thick white l-shape with black outline mid-top-left of canvas. */ -p5.prototype.beginShape = function(kind) { +p5.prototype.beginShape = function (kind) { p5._validateParameters('beginShape', arguments); if (this._renderer.isP3D) { this._renderer.beginShape(...arguments); @@ -287,9 +293,9 @@ p5.prototype.beginShape = function(kind) { } else { shapeKind = null; } - - vertices = []; - contourVertices = []; + shape = new Shape(); + // initialise first contour + shape.addContour(new Contour()); } return this; }; @@ -389,28 +395,26 @@ p5.prototype.beginShape = function(kind) { * @param {Number} z4 z-coordinate for the anchor point (for WebGL mode) * @chainable */ -p5.prototype.bezierVertex = function(...args) { +p5.prototype.bezierVertex = function (...args) { p5._validateParameters('bezierVertex', args); if (this._renderer.isP3D) { this._renderer.bezierVertex(...args); } else { - if (vertices.length === 0) { + let currentContour = shape.contourss[shape.contourss.length - 1]; + if (currentContour.segmentss.length === 0) { p5._friendlyError( 'vertex() must be used once before calling bezierVertex()', 'bezierVertex' ); } else { - isBezier = true; const vert = []; for (let i = 0; i < args.length; i++) { vert[i] = args[i]; } - vert.isVert = false; - if (isContour) { - contourVertices.push(vert); - } else { - vertices.push(vert); - } + + let cs = new ContourSegment2D(); + cs.addVertex(new BezierVertex(vert)); + currentContour.addSegment(cs); } } return this; @@ -514,13 +518,33 @@ p5.prototype.bezierVertex = function(...args) { * @alt * Upside-down u-shape line, mid canvas with the same shape in positive z-axis. */ -p5.prototype.curveVertex = function(...args) { +p5.prototype.curveVertex = function (...args) { p5._validateParameters('curveVertex', args); if (this._renderer.isP3D) { this._renderer.curveVertex(...args); } else { - isCurve = true; - this.vertex(args[0], args[1]); + let allVertObj = []; + + shape.contourss.forEach(c => { + c.segmentss.forEach(s => { + s.verticess.forEach(v => { + allVertObj.push(v); + }); + }); + }); + + let lastVertex = allVertObj.at(-1); + if (allVertObj.length === 0 || lastVertex.type === undefined) { + // first curveVertex + let cs = new ContourSegment2D(); + cs.addVertex(new CurveVertex(args[0], args[1])); + let currentContour = shape.contourss[shape.contourss.length - 1]; + currentContour.addSegment(cs); + } else { + lastVertex.data.push(args[0], args[1]); + } + + } return this; }; @@ -566,25 +590,22 @@ p5.prototype.curveVertex = function(...args) { * @alt * white rect and smaller grey rect with red outlines in center of canvas. */ -p5.prototype.endContour = function() { +p5.prototype.endContour = function () { if (this._renderer.isP3D) { return this; } - const vert = contourVertices[0].slice(); // copy all data - vert.isVert = contourVertices[0].isVert; - vert.moveTo = false; - contourVertices.push(vert); + let currentContour = shape.contourss[shape.contourss.length - 1]; + const vert = currentContour.firstSegment.verticess[0].coordinates.slice(); + let cs = new ContourSegment2D(); + cs.addVertex(new Vertex(vert)); + currentContour.addSegment(cs); - // prevent stray lines with multiple contours if (isFirstContour) { - vertices.push(vertices[0]); + // pushing first contour's first segment to the end + shape.contourss[0].addSegment(shape.contourss[0].segmentss[0]); isFirstContour = false; } - - for (let i = 0; i < contourVertices.length; i++) { - vertices.push(contourVertices[i]); - } return this; }; @@ -707,7 +728,7 @@ p5.prototype.endContour = function() { * @alt * Triangle line shape with smallest interior angle on bottom and upside-down L. */ -p5.prototype.endShape = function(mode, count = 1) { +p5.prototype.endShape = function (mode, count = 1) { p5._validateParameters('endShape', arguments); if (count < 1) { console.log('🌸 p5.js says: You can not have less than one instance'); @@ -728,7 +749,10 @@ p5.prototype.endShape = function(mode, count = 1) { if (count !== 1) { console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); } - if (vertices.length === 0) { + + let currentContour = shape.contourss[shape.contourss.length - 1]; + + if (currentContour.segmentss.length === 0) { return this; } if (!this._renderer._doStroke && !this._renderer._doFill) { @@ -739,19 +763,34 @@ p5.prototype.endShape = function(mode, count = 1) { // if the shape is closed, the first element is also the last element if (closeShape && !isContour) { - vertices.push(vertices[0]); + currentContour.addSegment(currentContour.firstSegment); } + // all verts from shape + let allVertObj = []; + shape.contourss.forEach(c => { + c.segmentss.forEach(s => { + s.verticess.forEach(v => { + allVertObj.push(v); + }); + }); + }); + this._renderer.endShape( mode, - vertices, isCurve, isBezier, isQuadratic, isContour, - shapeKind + shapeKind, + allVertObj, + currentContour, + closeShape ); + // Reset shape + shape.contourss.length = 0; + // Reset some settings isCurve = false; isBezier = false; @@ -763,7 +802,7 @@ p5.prototype.endShape = function(mode, count = 1) { // We must remove it again to prevent the list of vertices from growing // over successive calls to endShape(CLOSE) if (closeShape) { - vertices.pop(); + currentContour.segmentss.pop(); } } return this; @@ -887,7 +926,7 @@ p5.prototype.endShape = function(mode, count = 1) { * @alt * backwards s-shaped black line with the same s-shaped line in positive z-axis. */ -p5.prototype.quadraticVertex = function(...args) { +p5.prototype.quadraticVertex = function (...args) { p5._validateParameters('quadraticVertex', args); if (this._renderer.isP3D) { this._renderer.quadraticVertex(...args); @@ -905,18 +944,17 @@ p5.prototype.quadraticVertex = function(...args) { return this; } - if (vertices.length > 0) { - isQuadratic = true; + let currentContour = shape.contourss[shape.contourss.length - 1]; + + if (currentContour.segmentss.length > 0) { const vert = []; for (let i = 0; i < args.length; i++) { vert[i] = args[i]; } - vert.isVert = false; - if (isContour) { - contourVertices.push(vert); - } else { - vertices.push(vert); - } + + let cs = new ContourSegment2D(); + cs.addVertex(new QuadraticVertex(vert)); + currentContour.addSegment(cs); } else { p5._friendlyError( 'vertex() must be used once before calling quadraticVertex()', @@ -1075,12 +1113,11 @@ p5.prototype.quadraticVertex = function(...args) { * @param {Number} [v] the vertex's texture v-coordinate * @chainable */ -p5.prototype.vertex = function(x, y, moveTo, u, v) { +p5.prototype.vertex = function (x, y, moveTo, u, v) { if (this._renderer.isP3D) { this._renderer.vertex(...arguments); } else { const vert = []; - vert.isVert = true; vert[0] = x; vert[1] = y; vert[2] = 0; @@ -1088,18 +1125,12 @@ p5.prototype.vertex = function(x, y, moveTo, u, v) { vert[4] = 0; vert[5] = this._renderer._getFill(); vert[6] = this._renderer._getStroke(); + let currentContour = shape.contourss[shape.contourss.length - 1]; - if (moveTo) { - vert.moveTo = moveTo; - } - if (isContour) { - if (contourVertices.length === 0) { - vert.moveTo = true; - } - contourVertices.push(vert); - } else { - vertices.push(vert); - } + let cs = new ContourSegment2D(); + cs.addVertex(new Vertex(vert)); + + currentContour.addSegment(cs); } return this; }; @@ -1148,7 +1179,7 @@ p5.prototype.vertex = function(x, y, moveTo, u, v) { * @param {Number} z The z component of the vertex normal. * @chainable */ -p5.prototype.normal = function(x, y, z) { +p5.prototype.normal = function (x, y, z) { this._assert3d('normal'); p5._validateParameters('normal', arguments); this._renderer.normal(...arguments);