diff --git a/src/core/constants.js b/src/core/constants.js index f38b561789..8900741a4d 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -709,6 +709,18 @@ export const CLAMP = 'clamp'; */ export const MIRROR = 'mirror'; +// WEBGL GEOMETRY SHADING +/** + * @property {String} FLAT + * @final + */ +export const FLAT = 'flat'; +/** + * @property {String} SMOOTH + * @final + */ +export const SMOOTH = 'smooth'; + // DEVICE-ORIENTATION /** * @property {String} LANDSCAPE diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 825fd27efc..186e922461 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -9,6 +9,7 @@ //some of the functions are adjusted from Three.js(http://threejs.org) import p5 from '../core/main'; +import * as constants from 'constants'; /** * p5 Geometry class * @class p5.Geometry @@ -185,17 +186,148 @@ p5.Geometry = class Geometry { return n.mult(Math.asin(sinAlpha) / ln); } /** - * computes smooth normals per vertex as an average of each - * face. - * @method computeNormals - * @chainable - */ - computeNormals() { + * This function calculates normals for each face, where each vertex's normal is the average of the normals of all faces it's connected to. + * i.e computes smooth normals per vertex as an average of each face. + * + * When using `FLAT` shading, vertices are disconnected/duplicated i.e each face has its own copy of vertices. + * When using `SMOOTH` shading, vertices are connected/deduplicated i.e each face has its vertices shared with other faces. + * + * Options can include: + * - `roundToPrecision`: Precision value for rounding computations. Defaults to 3. + * + * @method computeNormals + * @param {String} [shadingType] shading type (`FLAT` for flat shading or `SMOOTH` for smooth shading) for buildGeometry() outputs. Defaults to `FLAT`. + * @param {Object} [options] An optional object with configuration. + * @chainable + * + * @example + *
+ * + * let helix; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * helix = buildGeometry(() => { + * beginShape(); + * + * for (let i = 0; i < TWO_PI * 3; i += 0.6) { + * let radius = 20; + * let x = cos(i) * radius; + * let y = sin(i) * radius; + * let z = map(i, 0, TWO_PI * 3, -30, 30); + * vertex(x, y, z); + * } + * endShape(); + * }); + * helix.computeNormals(); + * } + * function draw() { + * background(255); + * stroke(0); + * fill(150, 200, 250); + * lights(); + * rotateX(PI*0.2); + * orbitControl(); + * model(helix); + * } + * + *
+ * + * @alt + * A 3D helix using the computeNormals() function by default uses `FLAT` to create a flat shading effect on the helix. + * + * @example + *
+ * + * let star; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * star = buildGeometry(() => { + * beginShape(); + * for (let i = 0; i < TWO_PI; i += PI / 5) { + * let outerRadius = 60; + * let innerRadius = 30; + * let xOuter = cos(i) * outerRadius; + * let yOuter = sin(i) * outerRadius; + * let zOuter = random(-20, 20); + * vertex(xOuter, yOuter, zOuter); + * + * let nextI = i + PI / 5 / 2; + * let xInner = cos(nextI) * innerRadius; + * let yInner = sin(nextI) * innerRadius; + * let zInner = random(-20, 20); + * vertex(xInner, yInner, zInner); + * } + * endShape(CLOSE); + * }); + * star.computeNormals(SMOOTH); + * } + * function draw() { + * background(255); + * stroke(0); + * fill(150, 200, 250); + * lights(); + * rotateX(PI*0.2); + * orbitControl(); + * model(star); + * } + * + *
+ * + * @alt + * A star-like geometry, here the computeNormals(SMOOTH) is applied for a smooth shading effect. + * This helps to avoid the faceted appearance that can occur with flat shading. + */ + computeNormals(shadingType = constants.FLAT, { roundToPrecision = 3 } = {}) { const vertexNormals = this.vertexNormals; - const vertices = this.vertices; + let vertices = this.vertices; const faces = this.faces; let iv; + if (shadingType === constants.SMOOTH) { + const vertexIndices = {}; + const uniqueVertices = []; + + const getKey = vert => + `${vert.x.toFixed(roundToPrecision)},${vert.y.toFixed(roundToPrecision)},${vert.z.toFixed(roundToPrecision)}`; + + // loop through each vertex and add uniqueVertices + for (let i = 0; i < vertices.length; i++) { + const vertex = vertices[i]; + const key = getKey(vertex); + if (vertexIndices[key] === undefined) { + vertexIndices[key] = uniqueVertices.length; + uniqueVertices.push(vertex); + } + } + + // update face indices to use the deduplicated vertex indices + faces.forEach(face => { + for (let fv = 0; fv < 3; ++fv) { + const originalVertexIndex = face[fv]; + const originalVertex = vertices[originalVertexIndex]; + const key = getKey(originalVertex); + face[fv] = vertexIndices[key]; + } + }); + + // update edge indices to use the deduplicated vertex indices + this.edges.forEach(edge => { + for (let ev = 0; ev < 2; ++ev) { + const originalVertexIndex = edge[ev]; + const originalVertex = vertices[originalVertexIndex]; + const key = getKey(originalVertex); + edge[ev] = vertexIndices[key]; + } + }); + + // update the deduplicated vertices + this.vertices = vertices = uniqueVertices; + } + // initialize the vertexNormals array with empty vectors vertexNormals.length = 0; for (iv = 0; iv < vertices.length; ++iv) {