From 81a10f04c37a09c37a4139087edc5608252d44db Mon Sep 17 00:00:00 2001 From: Srinidhi-4419 Date: Fri, 2 Oct 2026 13:19:09 +0530 Subject: [PATCH 1/2] fix: preserve vertex attributes with smooth normals --- src/webgl/p5.Geometry.js | 77 +++++++++++++++++----------------- test/unit/webgl/p5.Geometry.js | 34 +++++++++++++++ 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 5f7dfeff78..10a23bb813 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -1193,74 +1193,75 @@ class Geometry { */ computeNormals(shadingType = constants.FLAT, { roundToPrecision = 3 } = {}) { const vertexNormals = this.vertexNormals; - let vertices = this.vertices; + const vertices = this.vertices; const faces = this.faces; let iv; if (shadingType === constants.SMOOTH) { const vertexIndices = {}; const uniqueVertices = []; + const originalToUnique = []; const power = Math.pow(10, roundToPrecision); const rounded = val => Math.round(val * power) / power; const getKey = vert => `${rounded(vert.x)},${rounded(vert.y)},${rounded(vert.z)}`; - // 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); } + + originalToUnique[i] = vertexIndices[key]; } - // 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]; - } - }); + const smoothNormals = []; - // 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]; + for (iv = 0; iv < uniqueVertices.length; ++iv) { + smoothNormals.push(new Vector(0, 0, 0)); + } + + faces.forEach((face, f) => { + const faceNormal = this._getFaceNormal(f); + + for (let fv = 0; fv < 3; ++fv) { + const vertexIndex = originalToUnique[face[fv]]; + smoothNormals[vertexIndex].add(faceNormal); } }); - // update the deduplicated vertices - this.vertices = vertices = uniqueVertices; - } + for (iv = 0; iv < smoothNormals.length; ++iv) { + smoothNormals[iv].normalize(); + } - // initialize the vertexNormals array with empty vectors - vertexNormals.length = 0; - for (iv = 0; iv < vertices.length; ++iv) { - vertexNormals.push(new Vector(0, 0, 0)); - } + vertexNormals.length = 0; - // loop through all the faces adding its normal to the normal - // of each of its vertices - faces.forEach((face, f) => { - const faceNormal = this._getFaceNormal(f); + for (iv = 0; iv < vertices.length; ++iv) { + vertexNormals.push(smoothNormals[originalToUnique[iv]].copy()); + } + } else { + vertexNormals.length = 0; - // all three vertices get the normal added - for (let fv = 0; fv < 3; ++fv) { - const vertexIndex = face[fv]; - vertexNormals[vertexIndex].add(faceNormal); + for (iv = 0; iv < vertices.length; ++iv) { + vertexNormals.push(new Vector(0, 0, 0)); } - }); - // normalize the normals - for (iv = 0; iv < vertices.length; ++iv) { - vertexNormals[iv].normalize(); + faces.forEach((face, f) => { + const faceNormal = this._getFaceNormal(f); + + for (let fv = 0; fv < 3; ++fv) { + const vertexIndex = face[fv]; + vertexNormals[vertexIndex].add(faceNormal); + } + }); + + for (iv = 0; iv < vertices.length; ++iv) { + vertexNormals[iv].normalize(); + } } return this; diff --git a/test/unit/webgl/p5.Geometry.js b/test/unit/webgl/p5.Geometry.js index 035237b091..2531bfca66 100644 --- a/test/unit/webgl/p5.Geometry.js +++ b/test/unit/webgl/p5.Geometry.js @@ -15,6 +15,40 @@ suite('p5.Geometry', function () { myp5.remove(); }); + suite('computeNormals', function () { + test('SMOOTH preserves vertices with different UVs', function () { + const geom = new p5.Geometry(); + + geom.vertices.push( + myp5.createVector(0, 0, 0), + myp5.createVector(1, 0, 0), + myp5.createVector(0, 1, 0), + myp5.createVector(0, 0, 0) + ); + + geom.uvs.push( + 0, 0, + 1, 0, + 0, 1, + 1, 1 + ); + + geom.faces.push( + [0, 1, 2], + [3, 2, 1] + ); + + const originalVertexCount = geom.vertices.length; + const originalUvs = [...geom.uvs]; + + geom.computeNormals(myp5.SMOOTH); + + expect(geom.vertices.length).toEqual(originalVertexCount); + expect(geom.uvs).toEqual(originalUvs); + expect(geom.vertexNormals[0]).toEqual(geom.vertexNormals[3]); + }); + }); + suite('computeTangents', function () { test('a uv-mapped triangle gets a +u tangent with correct handedness', function () { From 0c30b9960cc005aa906169cc929d03e889238efc Mon Sep 17 00:00:00 2001 From: Srinidhi-4419 Date: Sun, 4 Oct 2026 21:06:58 +0530 Subject: [PATCH 2/2] test: add visual test for smooth normals --- test/unit/visual/cases/webgl.js | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index e2e40d2843..5cd37797a0 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1100,6 +1100,71 @@ visualSuite('WebGL', function () { screenshot(); } ); + visualTest( + 'computeNormals(SMOOTH) preserves smooth normals', + function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + + const geom = p5.buildGeometry(() => { + const verticesPerRing = 20; + const rings = 20; + + for (let ring = 0; ring < rings - 1; ring++) { + p5.beginShape(p5.QUAD_STRIP); + + for (let i = 0; i <= verticesPerRing; i++) { + for (const ringOffset of [0, 1]) { + const y = p5.map( + ring + ringOffset, + 0, + rings, + 40, + -40 + ); + const angle = p5.map( + i, + 0, + verticesPerRing, + 0, + p5.TWO_PI + ); + + const position = p5.createVector(40, 0).rotate(angle); + + const radius = p5.noise( + 200 + position.x * 0.01, + 200 + y * 0.01, + 200 + position.y * 0.01 + ); + + const squishedPosition = p5.createVector( + position.x * radius, + y, + position.y * radius + ); + + p5.vertex( + squishedPosition.x, + squishedPosition.y, + squishedPosition.z + ); + } + } + + p5.endShape(); + } + }); + + geom.computeNormals(p5.SMOOTH); + + p5.background(255); + p5.normalMaterial(); + p5.rotateY(0.5); + p5.model(geom); + + screenshot(); + } + ); }); visualSuite('font data', () => {