/**
* @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;