Skip to content
77 changes: 39 additions & 38 deletions src/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

}

// 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;
Expand Down
65 changes: 65 additions & 0 deletions test/unit/visual/cases/webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"numScreenshots": 1
}
34 changes: 34 additions & 0 deletions test/unit/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down