/** * @module Shape * @submodule Vertex * @for p5 * @requires core * @requires constants */ import p5 from '../main'; import * as constants from '../constants'; 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 within shapes such as the center of the letter 'O'. beginContour() * begins recording vertices for the shape and endContour() stops recording. * The vertices that define a negative shape must "wind" in the opposite * direction from the exterior shape. First draw vertices for the exterior * clockwise order, then for internal shapes, draw vertices * shape in counter-clockwise. *

* These functions can only be used within a beginShape()/endShape() pair and * transformations such as translate(), rotate(), and scale() do not work * within a beginContour()/endContour() pair. It is also not possible to use * other shapes, such as ellipse() or rect() within. * * @method beginContour * @chainable * @example *
* * translate(50, 50); * stroke(255, 0, 0); * beginShape(); * // Exterior part of shape, clockwise winding * vertex(-40, -40); * vertex(40, -40); * vertex(40, 40); * vertex(-40, 40); * // Interior part of shape, counter-clockwise winding * beginContour(); * vertex(-20, -20); * vertex(-20, 20); * vertex(20, 20); * vertex(20, -20); * endContour(); * endShape(CLOSE); * *
* * @alt * white rect and smaller grey rect with red outlines in center of canvas. * */ p5.prototype.beginContour = function() { contourVertices = []; isContour = true; return this; }; /** * Using the beginShape() and endShape() functions allow creating more * complex forms. beginShape() begins recording vertices for a shape and * endShape() stops recording. The value of the kind parameter tells it which * types of shapes to create from the provided vertices. With no mode * specified, the shape can be any irregular polygon. *

* The parameters available for beginShape() are POINTS, LINES, TRIANGLES, * TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, QUAD_STRIP, and TESS (WebGL only). After calling the * beginShape() function, a series of vertex() commands must follow. To stop * drawing the shape, call endShape(). Each shape will be outlined with the * current stroke color and filled with the fill color. *

* Transformations such as translate(), rotate(), and scale() do not work * within beginShape(). It is also not possible to use other shapes, such as * ellipse() or rect() within beginShape(). * * @method beginShape * @param {Constant} [kind] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN * TRIANGLE_STRIP, QUADS, QUAD_STRIP or TESS * @chainable * @example *
* * beginShape(); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(CLOSE); * *
* *
* * beginShape(POINTS); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(); * *
* *
* * beginShape(LINES); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(); * *
* *
* * noFill(); * beginShape(); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(); * *
* *
* * noFill(); * beginShape(); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(CLOSE); * *
* *
* * beginShape(TRIANGLES); * vertex(30, 75); * vertex(40, 20); * vertex(50, 75); * vertex(60, 20); * vertex(70, 75); * vertex(80, 20); * endShape(); * *
* *
* * beginShape(TRIANGLE_STRIP); * vertex(30, 75); * vertex(40, 20); * vertex(50, 75); * vertex(60, 20); * vertex(70, 75); * vertex(80, 20); * vertex(90, 75); * endShape(); * *
* *
* * beginShape(TRIANGLE_FAN); * vertex(57.5, 50); * vertex(57.5, 15); * vertex(92, 50); * vertex(57.5, 85); * vertex(22, 50); * vertex(57.5, 15); * endShape(); * *
* *
* * beginShape(QUADS); * vertex(30, 20); * vertex(30, 75); * vertex(50, 75); * vertex(50, 20); * vertex(65, 20); * vertex(65, 75); * vertex(85, 75); * vertex(85, 20); * endShape(); * *
* *
* * beginShape(QUAD_STRIP); * vertex(30, 20); * vertex(30, 75); * vertex(50, 20); * vertex(50, 75); * vertex(65, 20); * vertex(65, 75); * vertex(85, 20); * vertex(85, 75); * endShape(); * *
* *
* * beginShape(); * vertex(20, 20); * vertex(40, 20); * vertex(40, 40); * vertex(60, 40); * vertex(60, 60); * vertex(20, 60); * endShape(CLOSE); * *
* @alt * white square-shape with black outline in middle-right of canvas. * 4 black points in a square shape in middle-right of canvas. * 2 horizontal black lines. In the top-right and bottom-right of canvas. * 3 line shape with horizontal on top, vertical in middle and horizontal bottom. * square line shape in middle-right of canvas. * 2 white triangle shapes mid-right canvas. left one pointing up and right down. * 5 horizontal interlocking and alternating white triangles in mid-right canvas. * 4 interlocking white triangles in 45 degree rotated square-shape. * 2 white rectangle shapes in mid-right canvas. Both 20x55. * 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._validateParameters('beginShape', arguments); if (this._renderer.isP3D) { this._renderer.beginShape(...arguments); } else { if ( kind === constants.POINTS || kind === constants.LINES || kind === constants.TRIANGLES || kind === constants.TRIANGLE_FAN || kind === constants.TRIANGLE_STRIP || kind === constants.QUADS || kind === constants.QUAD_STRIP ) { shapeKind = kind; } else { shapeKind = null; } vertices = []; contourVertices = []; } return this; }; /** * Specifies vertex coordinates for Bezier curves. Each call to * bezierVertex() defines the position of two control points and * one anchor point of a Bezier curve, adding a new segment to a * line or shape. For WebGL mode bezierVertex() can be used in 2D * as well as 3D mode. 2D mode expects 6 parameters, while 3D mode * expects 9 parameters (including z coordinates). *

* The first time bezierVertex() is used within a beginShape() * call, it must be prefaced with a call to vertex() to set the first anchor * point. This function must be used between beginShape() and endShape() * and only when there is no MODE or POINTS parameter specified to * beginShape(). * * @method bezierVertex * @param {Number} x2 x-coordinate for the first control point * @param {Number} y2 y-coordinate for the first control point * @param {Number} x3 x-coordinate for the second control point * @param {Number} y3 y-coordinate for the second control point * @param {Number} x4 x-coordinate for the anchor point * @param {Number} y4 y-coordinate for the anchor point * @chainable * * @example *
* * noFill(); * beginShape(); * vertex(30, 20); * bezierVertex(80, 0, 80, 75, 30, 75); * endShape(); * *
* * @alt * crescent-shaped line in middle of canvas. Points facing left. * * @example *
* * beginShape(); * vertex(30, 20); * bezierVertex(80, 0, 80, 75, 30, 75); * bezierVertex(50, 80, 60, 25, 30, 20); * endShape(); * *
* * @alt * white crescent shape in middle of canvas. Points facing left. * * @example *
* * function setup() { * createCanvas(100, 100, WEBGL); * setAttributes('antialias', true); * } * function draw() { * orbitControl(); * background(50); * strokeWeight(4); * stroke(255); * point(-25, 30); * point(25, 30); * point(25, -30); * point(-25, -30); * * strokeWeight(1); * noFill(); * * beginShape(); * vertex(-25, 30); * bezierVertex(25, 30, 25, -30, -25, -30); * endShape(); * * beginShape(); * vertex(-25, 30, 20); * bezierVertex(25, 30, 20, 25, -30, 20, -25, -30, 20); * endShape(); * } * *
* * @alt * crescent shape in middle of canvas with another crescent shape on positive z-axis. */ /** * @method bezierVertex * @param {Number} x2 * @param {Number} y2 * @param {Number} z2 z-coordinate for the first control point (for WebGL mode) * @param {Number} x3 * @param {Number} y3 * @param {Number} z3 z-coordinate for the second control point (for WebGL mode) * @param {Number} x4 * @param {Number} y4 * @param {Number} z4 z-coordinate for the anchor point (for WebGL mode) * @chainable */ p5.prototype.bezierVertex = function(...args) { p5._validateParameters('bezierVertex', args); if (this._renderer.isP3D) { this._renderer.bezierVertex(...args); } else { if (vertices.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); } } } return this; }; /** * Specifies vertex coordinates for curves. This function may only * be used between beginShape() and endShape() and only when there * is no MODE parameter specified to beginShape(). * For WebGL mode curveVertex() can be used in 2D as well as 3D mode. * 2D mode expects 2 parameters, while 3D mode expects 3 parameters. *

* The first and last points in a series of curveVertex() lines will be used to * guide the beginning and end of a the curve. A minimum of four * points is required to draw a tiny curve between the second and * third points. Adding a fifth point with curveVertex() will draw * the curve between the second, third, and fourth points. The * curveVertex() function is an implementation of Catmull-Rom * splines. * * @method curveVertex * @param {Number} x x-coordinate of the vertex * @param {Number} y y-coordinate of the vertex * @chainable * @example *
* * strokeWeight(5); * point(84, 91); * point(68, 19); * point(21, 17); * point(32, 91); * strokeWeight(1); * * noFill(); * beginShape(); * curveVertex(84, 91); * curveVertex(84, 91); * curveVertex(68, 19); * curveVertex(21, 17); * curveVertex(32, 91); * curveVertex(32, 91); * endShape(); * *
* * * @alt * Upside-down u-shape line, mid canvas. left point extends beyond canvas view. */ /** * @method curveVertex * @param {Number} x * @param {Number} y * @param {Number} [z] z-coordinate of the vertex (for WebGL mode) * @chainable * @example *
* * function setup() { * createCanvas(100, 100, WEBGL); * setAttributes('antialias', true); * } * function draw() { * orbitControl(); * background(50); * strokeWeight(4); * stroke(255); * * point(-25, 25); * point(-25, 25); * point(-25, -25); * point(25, -25); * point(25, 25); * point(25, 25); * * strokeWeight(1); * noFill(); * * beginShape(); * curveVertex(-25, 25); * curveVertex(-25, 25); * curveVertex(-25, -25); * curveVertex(25, -25); * curveVertex(25, 25); * curveVertex(25, 25); * endShape(); * * beginShape(); * curveVertex(-25, 25, 20); * curveVertex(-25, 25, 20); * curveVertex(-25, -25, 20); * curveVertex(25, -25, 20); * curveVertex(25, 25, 20); * curveVertex(25, 25, 20); * endShape(); * } * *
* * @alt * Upside-down u-shape line, mid canvas with the same shape in positive z-axis. * */ 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]); } return this; }; /** * Use the beginContour() and endContour() functions to create negative * shapes within shapes such as the center of the letter 'O'. beginContour() * begins recording vertices for the shape and endContour() stops recording. * The vertices that define a negative shape must "wind" in the opposite * direction from the exterior shape. First draw vertices for the exterior * clockwise order, then for internal shapes, draw vertices * shape in counter-clockwise. *

* These functions can only be used within a beginShape()/endShape() pair and * transformations such as translate(), rotate(), and scale() do not work * within a beginContour()/endContour() pair. It is also not possible to use * other shapes, such as ellipse() or rect() within. * * @method endContour * @chainable * @example *
* * translate(50, 50); * stroke(255, 0, 0); * beginShape(); * // Exterior part of shape, clockwise winding * vertex(-40, -40); * vertex(40, -40); * vertex(40, 40); * vertex(-40, 40); * // Interior part of shape, counter-clockwise winding * beginContour(); * vertex(-20, -20); * vertex(-20, 20); * vertex(20, 20); * vertex(20, -20); * endContour(); * endShape(CLOSE); * *
* * @alt * white rect and smaller grey rect with red outlines in center of canvas. * */ p5.prototype.endContour = function() { const vert = contourVertices[0].slice(); // copy all data vert.isVert = contourVertices[0].isVert; vert.moveTo = false; contourVertices.push(vert); // prevent stray lines with multiple contours if (isFirstContour) { vertices.push(vertices[0]); isFirstContour = false; } for (let i = 0; i < contourVertices.length; i++) { vertices.push(contourVertices[i]); } return this; }; /** * The endShape() function is the companion to beginShape() and may only be * called after beginShape(). When endShape() is called, all of image data * defined since the previous call to beginShape() is written into the image * buffer. The constant CLOSE as the value for the MODE parameter to close * the shape (to connect the beginning and the end). * * @method endShape * @param {Constant} [mode] use CLOSE to close the shape * @chainable * @example *
* * noFill(); * * beginShape(); * vertex(20, 20); * vertex(45, 20); * vertex(45, 80); * endShape(CLOSE); * * beginShape(); * vertex(50, 20); * vertex(75, 20); * vertex(75, 80); * endShape(); * *
* * @alt * Triangle line shape with smallest interior angle on bottom and upside-down L. * */ p5.prototype.endShape = function(mode) { p5._validateParameters('endShape', arguments); if (this._renderer.isP3D) { this._renderer.endShape( mode, isCurve, isBezier, isQuadratic, isContour, shapeKind ); } else { if (vertices.length === 0) { return this; } if (!this._renderer._doStroke && !this._renderer._doFill) { return this; } const closeShape = mode === constants.CLOSE; // if the shape is closed, the first element is also the last element if (closeShape && !isContour) { vertices.push(vertices[0]); } this._renderer.endShape( mode, vertices, isCurve, isBezier, isQuadratic, isContour, shapeKind ); // Reset some settings isCurve = false; isBezier = false; isQuadratic = false; isContour = false; isFirstContour = true; // If the shape is closed, the first element was added as last element. // We must remove it again to prevent the list of vertices from growing // over successive calls to endShape(CLOSE) if (closeShape) { vertices.pop(); } } return this; }; /** * Specifies vertex coordinates for quadratic Bezier curves. Each call to * quadraticVertex() defines the position of one control points and one * anchor point of a Bezier curve, adding a new segment to a line or shape. * The first time quadraticVertex() is used within a beginShape() call, it * must be prefaced with a call to vertex() to set the first anchor point. * For WebGL mode quadraticVertex() can be used in 2D as well as 3D mode. * 2D mode expects 4 parameters, while 3D mode expects 6 parameters * (including z coordinates). *

* This function must be used between beginShape() and endShape() * and only when there is no MODE or POINTS parameter specified to * beginShape(). * * @method quadraticVertex * @param {Number} cx x-coordinate for the control point * @param {Number} cy y-coordinate for the control point * @param {Number} x3 x-coordinate for the anchor point * @param {Number} y3 y-coordinate for the anchor point * @chainable * * @example *
* * strokeWeight(5); * point(20, 20); * point(80, 20); * point(50, 50); * * noFill(); * strokeWeight(1); * beginShape(); * vertex(20, 20); * quadraticVertex(80, 20, 50, 50); * endShape(); * *
* *
* * strokeWeight(5); * point(20, 20); * point(80, 20); * point(50, 50); * * point(20, 80); * point(80, 80); * point(80, 60); * * noFill(); * strokeWeight(1); * beginShape(); * vertex(20, 20); * quadraticVertex(80, 20, 50, 50); * quadraticVertex(20, 80, 80, 80); * vertex(80, 60); * endShape(); * *
* * @alt * arched-shaped black line with 4 pixel thick stroke weight. * backwards s-shaped black line with 4 pixel thick stroke weight. * */ /** * @method quadraticVertex * @param {Number} cx * @param {Number} cy * @param {Number} cz z-coordinate for the control point (for WebGL mode) * @param {Number} x3 * @param {Number} y3 * @param {Number} z3 z-coordinate for the anchor point (for WebGL mode) * @chainable * * @example *
* * function setup() { * createCanvas(100, 100, WEBGL); * setAttributes('antialias', true); * } * function draw() { * orbitControl(); * background(50); * strokeWeight(4); * stroke(255); * * point(-35, -35); * point(35, -35); * point(0, 0); * point(-35, 35); * point(35, 35); * point(35, 10); * * strokeWeight(1); * noFill(); * * beginShape(); * vertex(-35, -35); * quadraticVertex(35, -35, 0, 0); * quadraticVertex(-35, 35, 35, 35); * vertex(35, 10); * endShape(); * * beginShape(); * vertex(-35, -35, 20); * quadraticVertex(35, -35, 20, 0, 0, 20); * quadraticVertex(-35, 35, 20, 35, 35, 20); * vertex(35, 10, 20); * endShape(); * } * *
* * @alt * backwards s-shaped black line with the same s-shaped line in postive z-axis. */ p5.prototype.quadraticVertex = function(...args) { p5._validateParameters('quadraticVertex', args); if (this._renderer.isP3D) { this._renderer.quadraticVertex(...args); } else { //if we're drawing a contour, put the points into an // array for inside drawing if (this._contourInited) { const pt = {}; pt.x = args[0]; pt.y = args[1]; pt.x3 = args[2]; pt.y3 = args[3]; pt.type = constants.QUADRATIC; this._contourVertices.push(pt); return this; } if (vertices.length > 0) { isQuadratic = 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); } } else { p5._friendlyError( 'vertex() must be used once before calling quadraticVertex()', 'quadraticVertex' ); } } return this; }; /** * All shapes are constructed by connecting a series of vertices. vertex() * is used to specify the vertex coordinates for points, lines, triangles, * quads, and polygons. It is used exclusively within the beginShape() and * endShape() functions. * * @method vertex * @param {Number} x x-coordinate of the vertex * @param {Number} y y-coordinate of the vertex * @chainable * @example *
* * strokeWeight(3); * beginShape(POINTS); * vertex(30, 20); * vertex(85, 20); * vertex(85, 75); * vertex(30, 75); * endShape(); * *
* *
* * createCanvas(100, 100, WEBGL); * background(240, 240, 240); * fill(237, 34, 93); * noStroke(); * beginShape(); * vertex(0, 35); * vertex(35, 0); * vertex(0, -35); * vertex(-35, 0); * endShape(); * *
* *
* * createCanvas(100, 100, WEBGL); * background(240, 240, 240); * fill(237, 34, 93); * noStroke(); * beginShape(); * vertex(-10, 10); * vertex(0, 35); * vertex(10, 10); * vertex(35, 0); * vertex(10, -8); * vertex(0, -35); * vertex(-10, -8); * vertex(-35, 0); * endShape(); * *
* *
* * strokeWeight(3); * stroke(237, 34, 93); * beginShape(LINES); * vertex(10, 35); * vertex(90, 35); * vertex(10, 65); * vertex(90, 65); * vertex(35, 10); * vertex(35, 90); * vertex(65, 10); * vertex(65, 90); * endShape(); * *
* *
* * // Click to change the number of sides. * // In WebGL mode, custom shapes will only * // display hollow fill sections when * // all calls to vertex() use the same z-value. * * let sides = 3; * let angle, px, py; * * function setup() { * createCanvas(100, 100, WEBGL); * setAttributes('antialias', true); * fill(237, 34, 93); * strokeWeight(3); * } * * function draw() { * background(200); * rotateX(frameCount * 0.01); * rotateZ(frameCount * 0.01); * ngon(sides, 0, 0, 80); * } * * function mouseClicked() { * if (sides > 6) { * sides = 3; * } else { * sides++; * } * } * * function ngon(n, x, y, d) { * beginShape(TESS); * for (let i = 0; i < n + 1; i++) { * angle = TWO_PI / n * i; * px = x + sin(angle) * d / 2; * py = y - cos(angle) * d / 2; * vertex(px, py, 0); * } * for (let i = 0; i < n + 1; i++) { * angle = TWO_PI / n * i; * px = x + sin(angle) * d / 4; * py = y - cos(angle) * d / 4; * vertex(px, py, 0); * } * endShape(); * } * *
* @alt * 4 black points in a square shape in middle-right of canvas. * 4 points making a diamond shape. * 8 points making a star. * 8 points making 4 lines. * A rotating 3D shape with a hollow section in the middle. * */ /** * @method vertex * @param {Number} x * @param {Number} y * @param {Number} z z-coordinate of the vertex * @param {Number} [u] the vertex's texture u-coordinate * @param {Number} [v] the vertex's texture v-coordinate * @chainable */ 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; vert[3] = 0; vert[4] = 0; vert[5] = this._renderer._getFill(); vert[6] = this._renderer._getStroke(); if (moveTo) { vert.moveTo = moveTo; } if (isContour) { if (contourVertices.length === 0) { vert.moveTo = true; } contourVertices.push(vert); } else { vertices.push(vert); } } return this; }; export default p5;