29th August 2005, 12:23 PM | #1 |
Junior Member
Member
Join Date: Nov 2003
Posts: 12
|
How are AC3D Normal Calculations done?
I'm using ac3d files directly in an OpenGL program. In doing the normal calculations I notice a couple of things.
1) My method is significantly slower than AC3D 2) I smooth over adjacent surfaces that AC3D doesn't. Basically for each surface set to smooth I check for a shared vertex with any other surface in that object, then calculate the average of the surface normals of all smooth surfaces joining at that vertex to get the vertex normal. An illustration of the problem is a box drawn in ac3d and set to smooth surfaces doesn't look any different than the same box with the surfaces set to flat when viewed in AC3D. Using the normal calculation methodology above it looks like a poor sphere. What I would like to do is duplicate the surface normal calculation done in ac3d ( I know I can export normals, but I want to use the .ac file directly) Perhaps someone, (Andy?) can give a hint on determining which surfaces need to be included in the vertex normal calculation. thanks ges |
29th August 2005, 01:47 PM | #2 |
Senior Member
Professional user
Join Date: Jul 2003
Posts: 899
|
I believe AC3D does vertex smooth using all shared surfaces of a vertex.
You won't see the smoothing on a cube, however, because of the crease angle - you'll have to open the cube's object properties and push the Crease angle above 90 to see vertex smoothing in action. Sounds like your base calculation is fine, you'll just have to account for the surface angle. Also, you won't be able to get away with a single vertex normal where you have mixed smooth/flat angles due to crease (i.e., the vertices at the edge of a cylinder where the top is flat-shaded and the sides are smooth). Good luck Dennis |
29th August 2005, 03:06 PM | #3 |
Junior Member
Member
Join Date: Nov 2003
Posts: 12
|
I guess I was unfamiliar with the crease angle. I took your advice on the cube properties and that is indeed what I see in my OpenGL code.
Let's see if I understand the concept. If the angle between an adjacent surface and the surface for which I am calculating normals is greater than the crease angle I ignore that surface when calculating the per vertex normal. That makes sense, but, this adds another calculation in the loop for normals. I suppose I have a code optimisation problem, its at least an order of magnitude slower than AC3D for an object with 20000 verticies. I was certainly hoping there was a magic pill that would solve my problem. thanks ges |
29th August 2005, 03:32 PM | #4 |
Administrator
Professional user
Join Date: Jun 2003
Posts: 4,565
|
You have the right concept.
Here's AC3D's internal code that calculates the normals. Code:
// get the crease angle in radians float rad_angle = cos(ob->crease_angle * M_PI / 180.0f); ... { // vertex index will hold a list of each surface that references the vertex // first, clear the lists for (List *p = ob->pointlist; p != NULL; p = p->next) { Vertex *v = (Vertex *)p->data; vertex_set_index(v, NULL); } // for each sv, add the surface to the list of surfaces in each vertex for (List *spp = ob->surfacelist; spp != NULL; spp = spp->next) { Surface *s = (Surface *)spp->data; for (List *svp = s->vertlist; svp != NULL; svp = svp->next) { SVertex *sv = (SVertex *)svp->data; list_add_item_head((List **)&(sv->v->index), s); } } for (List *sp = ob->surfacelist; sp != NULL; sp = sp->next) { Surface *s = (Surface *)sp->data; for (List *svp = s->vertlist; svp != NULL; svp = svp->next) { SVertex *sv = (SVertex *)svp->data; sv->normal = s->normal; if ((vertex_get_index(sv->v) == 0)) { continue; // next please } // so, surface is shaded // fl is list of faces that refer to this vertex List *fl = (List *)vertex_get_index(sv->v); for (List *flp = fl; flp != NULL; flp = flp->next) { Surface *os = (Surface *)flp->data; // ignore if the same surface if (s != os) { // even if the surface is flat, we still calculate the normals // the shading may change and this saves recalulating normals again // calc dot product of this surface plus attached float dot = (DOTPRODUCT(&s->normal, &os->normal)); if (dot > rad_angle) { ADDPOINTS(&os->normal, &sv->normal); } } } normalize_point(&sv->normal); } } for (List *vp = ob->pointlist; vp != NULL; vp = vp->next) { Vertex *v = (Vertex *)vp->data; list_free((List **)&vertex_get_index(v)); } } 1) that the vertex_index stuff is internal and you'll need to use another way of storing the vertex info. 2) The surface flag for shading is used when drawing the model - if a surface is 'smooth', the vertex normals are used. If a surface is 'flat', the surface-normal is used for each vertex. 3) the above code calculates the all normals in an object (not just those changed recently). Some bits of internal code have been removed but I've hopefully left the important stuff. If the crease angle is 0 or 180, you can optimize a bit. |
29th August 2005, 04:50 PM | #5 |
Junior Member
Member
Join Date: Nov 2003
Posts: 12
|
Thanks Andy!
You are without a doubt the most responsive code developer I've ever dealt with. Thanks again. ges |
30th August 2005, 10:14 AM | #6 |
Junior Member
Member
Join Date: Nov 2003
Posts: 12
|
Just a follow up.
Thanks Andy, the code snippit above did provide the speedup I was looking for. I implemented the prefiltering of surfaces referencing each vertex using the STL as a vector of lists which is populated as I read in the file. This step is so obvious in retrospect I'm ashamed I didn't think of it. Thanks again. ges |
|
|