Skip to content

Commit

Permalink
Merge pull request #6553 from capGoblin/enhancement/computeNormals-de…
Browse files Browse the repository at this point in the history
…duplication

Update computeNormals() to support smooth shading for buildGeometry() outputs
  • Loading branch information
davepagurek authored Nov 17, 2023
2 parents 210bbe5 + edb3db6 commit 7246a60
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 7 deletions.
12 changes: 12 additions & 0 deletions src/core/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
146 changes: 139 additions & 7 deletions src/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
* <div>
* <code>
* 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);
* }
* </code>
* </div>
*
* @alt
* A 3D helix using the computeNormals() function by default uses `FLAT` to create a flat shading effect on the helix.
*
* @example
* <div>
* <code>
* 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);
* }
* </code>
* </div>
*
* @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) {
Expand Down

0 comments on commit 7246a60

Please sign in to comment.