UNPKG

processing-js

Version:

For the project website, visit http://processingjs.org

661 lines (656 loc) • 27.8 kB
module.exports = function(options) { var PConstants = options.PConstants, PMatrix2D = options.PMatrix2D, PMatrix3D = options.PMatrix3D; /** * Datatype for storing shapes. Processing can currently load and display SVG (Scalable Vector Graphics) shapes. * Before a shape is used, it must be loaded with the <b>loadShape()</b> function. The <b>shape()</b> function is used to draw the shape to the display window. * The <b>PShape</b> object contain a group of methods, linked below, that can operate on the shape data. * <br><br>The <b>loadShape()</b> method supports SVG files created with Inkscape and Adobe Illustrator. * It is not a full SVG implementation, but offers some straightforward support for handling vector data. * * @param {int} family the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY * * @see #shape() * @see #loadShape() * @see #shapeMode() */ var PShape = function(family) { this.family = family || PConstants.GROUP; this.visible = true; this.style = true; this.children = []; this.nameTable = []; this.params = []; this.name = ""; this.image = null; //type PImage this.matrix = null; this.kind = null; this.close = null; this.width = null; this.height = null; this.parent = null; }; /** * PShape methods * missing: findChild(), apply(), contains(), findChild(), getPrimitive(), getParams(), getVertex() , getVertexCount(), * getVertexCode() , getVertexCodes() , getVertexCodeCount(), getVertexX(), getVertexY(), getVertexZ() */ PShape.prototype = { /** * @member PShape * The isVisible() function returns a boolean value "true" if the image is set to be visible, "false" if not. This is modified with the <b>setVisible()</b> parameter. * <br><br>The visibility of a shape is usually controlled by whatever program created the SVG file. * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. * * @return {boolean} returns "true" if the image is set to be visible, "false" if not */ isVisible: function(){ return this.visible; }, /** * @member PShape * The setVisible() function sets the shape to be visible or invisible. This is determined by the value of the <b>visible</b> parameter. * <br><br>The visibility of a shape is usually controlled by whatever program created the SVG file. * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. * * @param {boolean} visible "false" makes the shape invisible and "true" makes it visible */ setVisible: function (visible){ this.visible = visible; }, /** * @member PShape * The disableStyle() function disables the shape's style data and uses Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. * Overrides this shape's style information and uses PGraphics styles and colors. Identical to ignoreStyles(true). Also disables styles for all child shapes. */ disableStyle: function(){ this.style = false; for(var i = 0, j=this.children.length; i<j; i++) { this.children[i].disableStyle(); } }, /** * @member PShape * The enableStyle() function enables the shape's style data and ignores Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. */ enableStyle: function(){ this.style = true; for(var i = 0, j=this.children.length; i<j; i++) { this.children[i].enableStyle(); } }, /** * @member PShape * The getFamily function returns the shape type * * @return {int} the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY */ getFamily: function(){ return this.family; }, /** * @member PShape * The getWidth() function gets the width of the drawing area (not necessarily the shape boundary). */ getWidth: function(){ return this.width; }, /** * @member PShape * The getHeight() function gets the height of the drawing area (not necessarily the shape boundary). */ getHeight: function(){ return this.height; }, /** * @member PShape * The setName() function sets the name of the shape * * @param {String} name the name of the shape */ setName: function(name){ this.name = name; }, /** * @member PShape * The getName() function returns the name of the shape * * @return {String} the name of the shape */ getName: function(){ return this.name; }, /** * @member PShape * Called by the following (the shape() command adds the g) * PShape s = loadShapes("blah.svg"); * shape(s); */ draw: function(renderContext) { if(!renderContext) { throw "render context missing for draw() in PShape"; } if (this.visible) { this.pre(renderContext); this.drawImpl(renderContext); this.post(renderContext); } }, /** * @member PShape * the drawImpl() function draws the SVG document. */ drawImpl: function(renderContext) { if (this.family === PConstants.GROUP) { this.drawGroup(renderContext); } else if (this.family === PConstants.PRIMITIVE) { this.drawPrimitive(renderContext); } else if (this.family === PConstants.GEOMETRY) { this.drawGeometry(renderContext); } else if (this.family === PConstants.PATH) { this.drawPath(renderContext); } }, /** * @member PShape * The drawPath() function draws the <path> part of the SVG document. */ drawPath: function(renderContext) { var i, j; if (this.vertices.length === 0) { return; } renderContext.beginShape(); if (this.vertexCodes.length === 0) { // each point is a simple vertex if (this.vertices[0].length === 2) { // drawing 2D vertices for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i][0], this.vertices[i][1]); } } else { // drawing 3D vertices for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i][0], this.vertices[i][1], this.vertices[i][2]); } } } else { // coded set of vertices var index = 0; if (this.vertices[0].length === 2) { // drawing a 2D path for (i = 0, j = this.vertexCodes.length; i < j; i++) { if (this.vertexCodes[i] === PConstants.VERTEX) { renderContext.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index].moveTo); renderContext.breakShape = false; index++; } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { renderContext.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1], this.vertices[index+1][0], this.vertices[index+1][1], this.vertices[index+2][0], this.vertices[index+2][1]); index += 3; } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { renderContext.curveVertex(this.vertices[index][0], this.vertices[index][1]); index++; } else if (this.vertexCodes[i] === PConstants.BREAK) { renderContext.breakShape = true; } } } else { // drawing a 3D path for (i = 0, j = this.vertexCodes.length; i < j; i++) { if (this.vertexCodes[i] === PConstants.VERTEX) { renderContext.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]); if (this.vertices[index].moveTo === true) { vertArray[vertArray.length-1].moveTo = true; } else if (this.vertices[index].moveTo === false) { vertArray[vertArray.length-1].moveTo = false; } renderContext.breakShape = false; } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { renderContext.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1], this.vertices[index+0][2], this.vertices[index+1][0], this.vertices[index+1][1], this.vertices[index+1][2], this.vertices[index+2][0], this.vertices[index+2][1], this.vertices[index+2][2]); index += 3; } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { renderContext.curveVertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]); index++; } else if (this.vertexCodes[i] === PConstants.BREAK) { renderContext.breakShape = true; } } } } renderContext.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN); }, /** * @member PShape * The drawGeometry() function draws the geometry part of the SVG document. */ drawGeometry: function(renderContext) { var i, j; renderContext.beginShape(this.kind); if (this.style) { for (i = 0, j = this.vertices.length; i < j; i++) { renderContext.vertex(this.vertices[i]); } } else { for (i = 0, j = this.vertices.length; i < j; i++) { var vert = this.vertices[i]; if (vert[2] === 0) { renderContext.vertex(vert[0], vert[1]); } else { renderContext.vertex(vert[0], vert[1], vert[2]); } } } renderContext.endShape(); }, /** * @member PShape * The drawGroup() function draws the <g> part of the SVG document. */ drawGroup: function(renderContext) { for (var i = 0, j = this.children.length; i < j; i++) { this.children[i].draw(renderContext); } }, /** * @member PShape * The drawPrimitive() function draws SVG document shape elements. These can be point, line, triangle, quad, rect, ellipse, arc, box, or sphere. */ drawPrimitive: function(renderContext) { if (this.kind === PConstants.POINT) { renderContext.point(this.params[0], this.params[1]); } else if (this.kind === PConstants.LINE) { if (this.params.length === 4) { // 2D renderContext.line(this.params[0], this.params[1], this.params[2], this.params[3]); } else { // 3D renderContext.line(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); } } else if (this.kind === PConstants.TRIANGLE) { renderContext.triangle(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); } else if (this.kind === PConstants.QUAD) { renderContext.quad(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5], this.params[6], this.params[7]); } else if (this.kind === PConstants.RECT) { if (this.image !== null) { var imMode = imageModeConvert; renderContext.imageMode(PConstants.CORNER); renderContext.image(this.image, this.params[0], this.params[1], this.params[2], this.params[3]); imageModeConvert = imMode; } else { var rcMode = renderContext.curRectMode; renderContext.rectMode(PConstants.CORNER); renderContext.rect(this.params[0], this.params[1], this.params[2], this.params[3]); renderContext.curRectMode = rcMode; } } else if (this.kind === PConstants.ELLIPSE) { var elMode = renderContext.curEllipseMode; renderContext.ellipseMode(PConstants.CORNER); renderContext.ellipse(this.params[0], this.params[1], this.params[2], this.params[3]); renderContext.curEllipseMode = elMode; } else if (this.kind === PConstants.ARC) { var eMode = curEllipseMode; renderContext.ellipseMode(PConstants.CORNER); renderContext.arc(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]); curEllipseMode = eMode; } else if (this.kind === PConstants.BOX) { if (this.params.length === 1) { renderContext.box(this.params[0]); } else { renderContext.box(this.params[0], this.params[1], this.params[2]); } } else if (this.kind === PConstants.SPHERE) { renderContext.sphere(this.params[0]); } }, /** * @member PShape * The pre() function performs the preparations before the SVG is drawn. This includes doing transformations and storing previous styles. */ pre: function(renderContext) { if (this.matrix) { renderContext.pushMatrix(); renderContext.transform(this.matrix); } if (this.style) { renderContext.pushStyle(); this.styles(renderContext); } }, /** * @member PShape * The post() function performs the necessary actions after the SVG is drawn. This includes removing transformations and removing added styles. */ post: function(renderContext) { if (this.matrix) { renderContext.popMatrix(); } if (this.style) { renderContext.popStyle(); } }, /** * @member PShape * The styles() function changes the Processing's current styles */ styles: function(renderContext) { if (this.stroke) { renderContext.stroke(this.strokeColor); renderContext.strokeWeight(this.strokeWeight); renderContext.strokeCap(this.strokeCap); renderContext.strokeJoin(this.strokeJoin); } else { renderContext.noStroke(); } if (this.fill) { renderContext.fill(this.fillColor); } else { renderContext.noFill(); } }, /** * @member PShape * The getChild() function extracts a child shape from a parent shape. Specify the name of the shape with the <b>target</b> parameter or the * layer position of the shape to get with the <b>index</b> parameter. * The shape is returned as a <b>PShape</b> object, or <b>null</b> is returned if there is an error. * * @param {String} target the name of the shape to get * @param {int} index the layer position of the shape to get * * @return {PShape} returns a child element of a shape as a PShape object or null if there is an error */ getChild: function(child) { var i, j; if (typeof child === 'number') { return this.children[child]; } var found; if(child === "" || this.name === child){ return this; } if(this.nameTable.length > 0) { for(i = 0, j = this.nameTable.length; i < j || found; i++) { if(this.nameTable[i].getName === child) { found = this.nameTable[i]; break; } } if (found) { return found; } } for(i = 0, j = this.children.length; i < j; i++) { found = this.children[i].getChild(child); if(found) { return found; } } return null; }, /** * @member PShape * The getChildCount() returns the number of children * * @return {int} returns a count of children */ getChildCount: function () { return this.children.length; }, /** * @member PShape * The addChild() adds a child to the PShape. * * @param {PShape} child the child to add */ addChild: function( child ) { this.children.push(child); child.parent = this; if (child.getName() !== null) { this.addName(child.getName(), child); } }, /** * @member PShape * The addName() functions adds a shape to the name lookup table. * * @param {String} name the name to be added * @param {PShape} shape the shape */ addName: function(name, shape) { if (this.parent !== null) { this.parent.addName( name, shape ); } else { this.nameTable.push( [name, shape] ); } }, /** * @member PShape * The translate() function specifies an amount to displace the shape. The <b>x</b> parameter specifies left/right translation, the <b>y</b> parameter specifies up/down translation, and the <b>z</b> parameter specifies translations toward/away from the screen. * Subsequent calls to the method accumulates the effect. For example, calling <b>translate(50, 0)</b> and then <b>translate(20, 0)</b> is the same as <b>translate(70, 0)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * <br><br>Using this method with the <b>z</b> parameter requires using the P3D or OPENGL parameter in combination with size. * * @param {int|float} x left/right translation * @param {int|float} y up/down translation * @param {int|float} z forward/back translation * * @see PMatrix2D#translate * @see PMatrix3D#translate */ translate: function() { if(arguments.length === 2) { this.checkMatrix(2); this.matrix.translate(arguments[0], arguments[1]); } else { this.checkMatrix(3); this.matrix.translate(arguments[0], arguments[1], 0); } }, /** * @member PShape * The checkMatrix() function makes sure that the shape's matrix is 1) not null, and 2) has a matrix * that can handle <em>at least</em> the specified number of dimensions. * * @param {int} dimensions the specified number of dimensions */ checkMatrix: function(dimensions) { if(this.matrix === null) { if(dimensions === 2) { this.matrix = new PMatrix2D(); } else { this.matrix = new PMatrix3D(); } }else if(dimensions === 3 && this.matrix instanceof PMatrix2D) { this.matrix = new PMatrix3D(); } }, /** * @member PShape * The rotateX() function rotates a shape around the x-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateX(HALF_PI)</b> and then <b>rotateX(HALF_PI)</b> is the same as <b>rotateX(PI)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateX */ rotateX: function(angle) { this.rotate(angle, 1, 0, 0); }, /** * @member PShape * The rotateY() function rotates a shape around the y-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateY(HALF_PI)</b> and then <b>rotateY(HALF_PI)</b> is the same as <b>rotateY(PI)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateY */ rotateY: function(angle) { this.rotate(angle, 0, 1, 0); }, /** * @member PShape * The rotateZ() function rotates a shape around the z-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateZ(HALF_PI)</b> and then <b>rotateZ(HALF_PI)</b> is the same as <b>rotateZ(PI)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. * * @param {float}angle angle of rotation specified in radians * * @see PMatrix3D#rotateZ */ rotateZ: function(angle) { this.rotate(angle, 0, 0, 1); }, /** * @member PShape * The rotate() function rotates a shape the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. * Transformations apply to everything that happens after and subsequent calls to the method accumulates the effect. * For example, calling <b>rotate(HALF_PI)</b> and then <b>rotate(HALF_PI)</b> is the same as <b>rotate(PI)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * If optional parameters x,y,z are supplied, the rotate is about the point (x, y, z). * * @param {float}angle angle of rotation specified in radians * @param {float}x x-coordinate of the point * @param {float}y y-coordinate of the point * @param {float}z z-coordinate of the point * @see PMatrix2D#rotate * @see PMatrix3D#rotate */ rotate: function() { if(arguments.length === 1){ this.checkMatrix(2); this.matrix.rotate(arguments[0]); } else { this.checkMatrix(3); this.matrix.rotate(arguments[0], arguments[1], arguments[2], arguments[3]); } }, /** * @member PShape * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. Shapes always scale from the relative origin of their bounding box. * Scale values are specified as decimal percentages. For example, the method call <b>scale(2.0)</b> increases the dimension of a shape by 200%. * Subsequent calls to the method multiply the effect. For example, calling <b>scale(2.0)</b> and then <b>scale(1.5)</b> is the same as <b>scale(3.0)</b>. * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. * <br><br>Using this fuction with the <b>z</b> parameter requires passing P3D or OPENGL into the size() parameter. * * @param {float}s percentage to scale the object * @param {float}x percentage to scale the object in the x-axis * @param {float}y percentage to scale the object in the y-axis * @param {float}z percentage to scale the object in the z-axis * * @see PMatrix2D#scale * @see PMatrix3D#scale */ scale: function() { if(arguments.length === 2) { this.checkMatrix(2); this.matrix.scale(arguments[0], arguments[1]); } else if (arguments.length === 3) { this.checkMatrix(2); this.matrix.scale(arguments[0], arguments[1], arguments[2]); } else { this.checkMatrix(2); this.matrix.scale(arguments[0]); } }, /** * @member PShape * The resetMatrix() function resets the matrix * * @see PMatrix2D#reset * @see PMatrix3D#reset */ resetMatrix: function() { this.checkMatrix(2); this.matrix.reset(); }, /** * @member PShape * The applyMatrix() function multiplies this matrix by another matrix of type PMatrix3D or PMatrix2D. * Individual elements can also be provided * * @param {PMatrix3D|PMatrix2D} matrix the matrix to multiply by * * @see PMatrix2D#apply * @see PMatrix3D#apply */ applyMatrix: function(matrix) { if (arguments.length === 1) { this.applyMatrix(matrix.elements[0], matrix.elements[1], 0, matrix.elements[2], matrix.elements[3], matrix.elements[4], 0, matrix.elements[5], 0, 0, 1, 0, 0, 0, 0, 1); } else if (arguments.length === 6) { this.checkMatrix(2); this.matrix.apply(arguments[0], arguments[1], arguments[2], 0, arguments[3], arguments[4], arguments[5], 0, 0, 0, 1, 0, 0, 0, 0, 1); } else if (arguments.length === 16) { this.checkMatrix(3); this.matrix.apply(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12], arguments[13], arguments[14], arguments[15]); } } }; return PShape; };