I’m working on some code to generate vertex normals based on a cutoff-angle (ie. the current facet normal won’t be included in the average if the dot product between it and previous facet normal are beyond a certain angle, say 90 degrees). But my code is producing basic ‘averaged’ normals for a cube which is no good.

Can someone point out what’s wrong with this? It should be fairly self-explanatory:

// first calculate all facet normals
for (int i = 0; i < mTriangles.size(); i++) {
mTriangles[i]->computeNormal();
}
// calculate vertex normals
for (unsigned short i = 0; i < mVertices.size(); i++)
{
Vertex::Vertex *vert = mVertices[i];
int avgCount = 1;
const float cos_angle = cos(90.0f * kDegToRad);
Poly *pp = vert->getAdjTriangle(0); // get first triangle
Vec3 lastPolyNormal = pp->getNormal(); // get first normal
vert->mNormal = lastPolyNormal; // set to first facet normal
// loop through all adjacent faces to this vertex
for (int j = 0; j < vert->getAdjTriangleCount(); j++)
{
Poly *ppp = vert->getAdjTriangle(j); // get next face
Vec3 currentNormal = ppp->getNormal(); // get face normal
// is angle between this and previous face more than angle
if (currentNormal.dot(lastPolyNormal) > cos_angle)
{
vert->mNormal += currentNormal;
avgCount++;
// record last face normal used
lastPolyNormal = currentNormal;
}
else {
vert->mNormal = currentNormal;
}
}
if (avgCount > 0) {
vert->mNormal /= avgCount;
}
}

Sorry, but didn’t this come up in the alogs forum recently? This is a math and algos question, isn’t it? Averaging normals is simply a matter a giving each one in the vertex ring the desired weight, either by a predetermined subdivision/limit criteria or by aesthetics. There’s nothing carved in stone anywhere that I know of.

I haven’t posted this question before, although I’m sure it comes up a lot.

The problem is the above code does not work - it produces standard ‘averaged’ normals and adjusting the angle has no affect. I’m hoping you guys can spot my mistake.

The only other code I’ve seen that does this was written in C; it was non OOP and very hard to follow.

// is angle between this and previous face more than angle
if (currentNormal.dot(lastPolyNormal) > cos_angle)

The Cosine (and thus the dot product) has its maximum for an angle of 0 degrees, and its minimum for an angle of 180 degrees.

The else part of that if statement is also broken. You also have to reset avgCount to 1, and set lastPolyNormal to currentNormal, just as in the then-part.

1.) The for-loop with j should start at 1, because vert->getAdjTriangle(0)->getNormal() is already the initial face normal.
2.) It looks wrong to compare against a normal which is moving. That is, lastnormal should remain constant.
3.) The normals outside the crease threshold should be ignored and not moved to the vert->mNormal!
4.) The averaging is wrong. The right thing would be to normalize the final result normal.

I mean like this:

// first calculate all facet normals
for (int i = 0; i < mTriangles.size(); i++)
{
mTriangles[i]->computeNormal();
}
const float cos_angle = cos(90.0f * kDegToRad); // == 0.0.
// calculate vertex normals
for (unsigned short i = 0; i < mVertices.size(); i++)
{
Vertex::Vertex *vert = mVertices[i];
Poly *pp = vert->getAdjTriangle(0); // get first triangle
Vec3 facetNormal = pp->getNormal(); // get first normal
vert->mNormal = facetNormal; // set to first facet normal
// loop through all adjacent faces to this vertex
for (int j = 1; j < vert->getAdjTriangleCount(); j++)
{
Poly *ppp = vert->getAdjTriangle(j); // get next face
Vec3 adjacentNormal = ppp->getNormal(); // get face normal
// is angle between this and initial face inside crease threshold
if (facetNormal.dot(adjacentNormal) > cos_angle)
{
vert->mNormal += adjacentNormal; // combine
}
// else ignore the other face.
}
normalize(vert->mNormal);
}

Mind that vertices cannot be reused if you have a crease with distinct normals, these need to go into independent primitives for rendering.

memfr0b, the comparison against the cosine is correct, the comment was wrong.