Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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