Relationship of Normal list to Face List

BlitzMax Forums/OpenGL Module/Relationship of Normal list to Face List

PaulJG(Posted 2005) [#1]
I'm trying to convert over my deathly slow 3d engine over to a more lean and mean model.. one thing I want to impliment is vertex arrays.

Just about got these suckers working right, but then the normals come into play.

Seems I must supply a list of vertex normals over to the array for each vertex of my shape.. but it seems most shape exporters from 3d packages - perfer to use a normal face list to reference the correct normal vertex. (which was fine for my original engine, but I want SPEED !)

Anyway, here's some info from the 3d file:
Mesh {
 20;
 1.41396;1.41905;1.41611;,
 1.41396;1.41905;-1.42199;,
 -1.42414;1.41905;-1.42199;,
 -1.42414;1.41905;1.41611;,
 1.41396;1.41905;-1.42199;,
 1.41396;-1.41905;-1.42199;,
 -1.42414;-1.41905;-1.42199;,
 -1.42414;1.41905;-1.42199;,
 1.41396;-1.41905;-1.42199;,
 1.41396;-1.41905;1.41611;,
 -1.42414;-1.41905;1.41611;,
 -1.42414;-1.41905;-1.42199;,
 1.41396;-1.41905;1.41611;,
 1.41396;1.41905;1.41611;,
 -1.42414;1.41905;1.41611;,
 -1.42414;-1.41905;1.41611;,
 1.41396;1.41905;-1.42199;,
 1.41396;1.41905;1.41611;,
 -1.42414;1.41905;1.41611;,
 -1.42414;1.41905;-1.42199;;
 
 12;
 3;0,1,2;,
 3;0,2,3;,
 3;4,5,6;,
 3;4,6,7;,
 3;8,9,10;,
 3;8,10,11;,
 3;12,13,14;,
 3;12,14,15;,
 3;12,5,16;,
 3;12,16,17;,
 3;18,19,6;,
 3;18,6,15;;

 MeshNormals {
  6;
  0.000000;1.000000;0.000000;,
  0.000000;0.000000;-1.000000;,
  0.000000;-1.000000;-0.000000;,
  0.000000;0.000000;1.000000;,
  1.000000;0.000000;0.000000;,
  -1.000000;0.000000;0.000000;;
  
12;
  3;0,0,0;,
  3;0,0,0;,
  3;1,1,1;,
  3;1,1,1;,
  3;2,2,2;,
  3;2,2,2;,
  3;3,3,3;,
  3;3,3,3;,
  3;4,4,4;,
  3;4,4,4;,
  3;5,5,5;,
  3;5,5,5;;


Can some kind-person please help me to find the relationship between the faces.. and the normal faces - so I can create a full normal list. After 3 days of working on it, I still cant seem to find the pattern.


col(Posted 2005) [#2]
This looks like a .x file to me ;)

OK From the top:
The first chunk is 20 vertex positions. If this is a cube then there is a face missing coz there should be 24. The reason for this so that the texture mapping will work properly. Each face of the cube has 4 corners (vertices) times that by 6 and you get 24. I'm only guessing about the shape, I am probably wrong.

Go with the guess that its a cube to make it a bit easier to understand:-

The next chunk has 12 triangle polygons. 2 triangles can make a square ( one side of the cube ) times by 6 sides give you the 12 triangles. This list is of indices into the vertex list. (index 0 based) EG. The first list of indices are 0,1,2. This means vertex 0,vertex 1 and vertex 2 are used to make triangle 1, or actually triangle 0 coz of 0 indexing (zero is a number with computers).

The next chunk is a number of mesh normals. These are the vertex normals that can be assigned to each vertex. Just because there is only six doesn't mean anything coz they are not the actual normals linked directly to the vertex, they are just a list of normals available. Imagine the fact that a list of vertices dont make the shape, its the way a triangle is made up of the vertices, that makes the shape of the polygon.


The last chunk of 12 is the normal list for each triangle. Remember each triangle has 3 vertices and each vertex will need a normal. In that list, the first entry, you have 0,0,0. This means: Triangle 0 ( coz its first in the list) -> vertex 0 -> will use normal index 0 which is 0.00 , 1.00 , 0.00. Triangle 0 -> vertex 1 -> will use normal 0 and trinagle 0 -> vertex 2 will use normal index 0. That info is the first line in this list. The next line is the same data but will be used for Triangle 1. The next line is for triangle 2 which is : Vertex 0 -> normal 1, Vertex 1 -> Normal 1 and Vertex 2 -> Normal 1. Again the same info is repeated in the next line for trinagle 3. As work through the list, you will be assinging each vertex used in each triangle an index into the normal list.

All you need to do is index in the normal list and assign a value in array that corresponds to the same vertex in your vertex array. Like :-

VertexArray[0] ... x..y..z..
NormalArray[0] ... nx.ny.nz
VertexArray[1] ... x..y..z..
NormalArray[1] ... nx.ny.nz.

You can use the glVertexPointer to point to the vertex array and glNormalPointer to point to the normal array. Depends on how you want to access the vertex/normal data. You could interleave them in one array so the data would flow as vx,vy,vz,nx,ny,nz,vx,vy,vz,nx,ny,nz.
Just depends on how to want to store and access the data.

Hope this isn't too dificult to read and understand as I was sort of brain-storming this as I typed it.


col(Posted 2005) [#3]
PS.
To find a face normal you can take two vectors that lie on the plane of the triangle face and find the cross-product of these vectors. EG a vector from Vertex 0 to Vertex 1, and a vector from vertex 0 to vertex 2 will give 2 two vectors that lie on the plane of the triangle face. Find the cross product of these 2 vectors and you have the normal in which the trinagle is facing. This calculation will rely on your trinagles all following the same rule of having the vertices defined either clockwise or anti-clockwise as you look at them from the 'front'.

pps. I can't spell 'triangle' properly when typing fast.


PaulJG(Posted 2005) [#4]
Thanks Col, some good advice.. but thats where the fun begins.

Yes it is a cube, and no theres not a face missing. Its sharing some of the vertexes.

Thats what I cant see, its got 20 verts.. but it should have 24..

So the face data and face normals dont tie up properly with the main vertexes.

Maybe the only way I can see forward is to read through the shape file, adding vertexes when needed - so each face (3 sided tri) uses a seperate vertex, and then calculating the normals from them - rather then from the shape file. :(


col(Posted 2005) [#5]
You only need to generate a seperate vertex for each face when you are texture mapping and that vertex is the corner of two polygons that have different textures. Or when you want to have 'solid' edges between each polygon. Getting back to the cube : You only really need 8 vertices and 12 trinagles to generate a cube, but you get problems when using vertex normals ( as each vertex normal is an average of the number of polygon normals that share that vertex ) as the shading will try to 'smooth out' the corners. If you generate 24 vertices then assign the correct normals, then you will get 'hard' edges that are associatted with a cube.

Perhaps you are struggling with the averaging of the vertex normals?
Did you use a 3d app to generate this file?


PaulJG(Posted 2005) [#6]
I've no idea what that means, sorry ? ;)

And yes.. the file is a X file from a 3d modeller. Dont want to hardcode anything as each file will be different.

The shape file for a sphere is fine.. seems the more simple the shape the more it starts to share faces/vertexes

Either way, gotta make the normal data for the lighting.

Time to get the pen and paper out, and follow through your example above.


col(Posted 2005) [#7]
sorry. I'll try again

I will use the words 'gfx card' loosely meaning the render engine.

A vertex normal tells the graphics card what way the vertex is facing so the gfx card can calculate lighting. A vertex normal will also have what is called 'u,v' data which is used when texture mapping, but I won't go into that yet and confuse things further :p
If you have a 2 polygons that share a single vertex ( in the case of a cube there are 3 faces that share a vertex on each corner of the cube ) then the 'proper' way to calculate that vertex normal is to add each polygons face normal together and divide that result by the number of polygons that share that vertex. In the cube example - say you defined the cube as having 8 vertices ( there are only 8 corners to a cube ), this means when you come to define the polygons, then 3 polygons will be sharing each vertex. So because 3 polys would share 1 vertex, to calculate that 1 vertex normal you would add the 'x' component of the 3 POLYGON FACE NORMALs together, add the 'y' component of the polygon face normals together, add the 'z' component of the polygon face normals together and then divide each 'x','y',and 'z' result by 3. This will give you the vertex normal.
This will mean that the corner of the vertex will point outwards from the centre of the cube, which, when it gets lit by the gfx algos will 'smooth' out the corners.
If on the other hand, you define 3 SEPERATE vertices for each triangle you can have the 3 vertex normals pointing in the same direction as the polygon face normal. If you look carefully through the vertex data in your cube example, you will notice that there is only 8 different vertex positions, but they are duplicated in the list to give you the 20 vertice data.

some more : ;)
The vertex normal data tells the gfx card which way a particular vertex is pointing ( used for lighting calculations ). If you have a normal of 0.0,1.0,0.0 this tells the gfx card that the vertex is pointing towards the 'positive Y axis'. If you have -1.0,0.0,0.0 then it means the negative X axis. All NORMAL ( polygon and vertex normals ) values will range from -1.0 to 1.0 because it is an indication as to which way towards a particular AXIS the normal is pointing.

So to make a cube actually look like a cube, you need to define 4 vertices for each face ( because each face will be pointing in a different direction ), then create 2 triangles using those 4 vertices. This will mean each vertex of these 2 triangles will have the SAME normal direction data as well, so you only need 1 lot of normal data that will be shared among these 4 vertices.
You need to repeat this for the other 5 faces which will give you a grand total of 24 vertices and 12 triangles. You should up with 6 lots of normal data. Each vertex normal will index into 1 of those 6.

Sorry to say mate, but it looks to me like something has gone wrong when outputting that data to that file.


col(Posted 2005) [#8]
Looking at the data, poly 7 uses vertex 15 and poly 12 uses vertex 15. but further down in the normal data poly 7 is indexing to normal data 3 and poly 12 is indexing into normal 5. This is giving you 2 different normal data to the same vertex !!! The latter entry will overwrite the former.

So there is definately a problem with the file, not your calculations


PaulJG(Posted 2005) [#9]
Excellently explained my friend, learnt quite a lot from that !. :)

At the mo my old engine worked fine, it would load in the file.. texture it (but not the file above), light it.. and render it just fine.

But that was because I was using the standard vertex3f, glNormal3f and glTexCoord2f commands - and it didnt have to be in a strict order, I could get away with using indirect pointers..

Here's some of the old code:
glBegin(GL_TRIANGLES)
For t=0 To obj_ptr.num_of_faces-1

glNormal3f(obj_ptr.normx[obj_ptr.norm1[t]],obj_ptr.normy[obj_ptr.norm2[t]],obj_ptr.normz[obj_ptr.norm3[t]])

glColor3f(obj_ptr.col_mat[obj_ptr.facemat[t],0],obj_ptr.col_mat[obj_ptr.facemat[t],1],obj_ptr.col_mat[obj_ptr.facemat[t],2])

glTexCoord2f(obj_ptr.texx[obj_ptr.face1[t]], obj_ptr.texy[obj_ptr.face1[t]])
glVertex3f(obj_ptr.vertx[obj_ptr.face1[t]], obj_ptr.verty[obj_ptr.face1[t]], obj_ptr.vertz[obj_ptr.face1[t]])	

glTexCoord2f(obj_ptr.texx[obj_ptr.face2[t]], obj_ptr.texy[obj_ptr.face2[t]])
glVertex3f(obj_ptr.vertx[obj_ptr.face2[t]], obj_ptr.verty[obj_ptr.face2[t]], obj_ptr.vertz[obj_ptr.face2[t]])	

glTexCoord2f(obj_ptr.texx[obj_ptr.face3[t]], obj_ptr.texy[obj_ptr.face3[t]])
glVertex3f(obj_ptr.vertx[obj_ptr.face3[t]], obj_ptr.verty[obj_ptr.face3[t]], obj_ptr.vertz[obj_ptr.face3[t]])	

Next
glEnd()

But now because of the move to vertex arrays, everythings gotta tie up in the arrays.

If only the normalpointer command took a list of normal faces and not the normal vert coords info. :(

I've just had a look at a cube exported by 3dsmax in X format, seems its quite a common thing to share the vertex normals
xof 0303txt 0032

Mesh PDXBox01 {
 8;
 -79.710144;0.000000;-77.777779;,
 92.270538;0.000000;71.980675;,
 -79.710144;0.000000;71.980675;,
 92.270538;0.000000;-77.777779;,
 -79.710144;61.835747;-77.777779;,
 92.270538;61.835747;71.980675;,
 92.270538;61.835747;-77.777779;,
 -79.710144;61.835747;71.980675;;
 12;
 3;0,1,2;,
 3;1,0,3;,
 3;4,5,6;,
 3;5,4,7;,
 3;0,6,3;,
 3;6,0,4;,
 3;3,5,1;,
 3;5,3,6;,
 3;1,7,2;,
 3;7,1,5;,
 3;2,4,0;,
 3;4,2,7;;

 MeshNormals  {
  6;
  0.000000;-1.000000;0.000000;,
  0.000000;1.000000;0.000000;,
  0.000000;0.000000;-1.000000;,
  1.000000;0.000000;0.000000;,
  0.000000;0.000000;1.000000;,
  -1.000000;0.000000;0.000000;;
  12;
  3;0,0,0;,
  3;0,0,0;,
  3;1,1,1;,
  3;1,1,1;,
  3;2,2,2;,
  3;2,2,2;,
  3;3,3,3;,
  3;3,3,3;,
  3;4,4,4;,
  3;4,4,4;,
  3;5,5,5;,
  3;5,5,5;;
 }

 MeshMaterialList  {
  1;
  12;
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0;

  Material {
   0.345098;0.345098;0.882353;1.000000;;
   0.000000;
   0.345098;0.345098;0.882353;;
   0.000000;0.000000;0.000000;;
  }
 }
}


But theres just got to be a way of doing this !???.
Whats really driving me up the wall at the mo is that my old engine could load models of any complicity no problems.. (I had a whole game level up & running).. but now, I'm stuck on a cube !? ;)


marksibly(Posted 2005) [#10]
Hi,

The number of vertex/normal 'values' doesn't matter, its the index lists that follow them that count.

Each row of the first index list specifies the vertices of a triangle.

Each row of the second index list specifies the normals for each vertex of the corresponding triangle.

This effectively means that to generate vertex arrays you'll have to duplicate values within the arrays, but that's OK.


col(Posted 2005) [#11]
Whats really driving me up the wall at the mo is that my old engine could load models of any complicity no problems.. (I had a whole game level up & running).. but now, I'm stuck on a cube !? ;)


And it gets even better when you start to do the texture mapping!!

Stick with it, and you will grasp the concept.
Like Mark says, you will have to have duplicate entries in the vertex array and the normal array, but it doesn't matter as long as the arrays are in 'sync', so to speak.
The principle of a triangle having its own vertices instead of sharing its vertices with another triangle will become very clear when you start texture mapping. As each vertex will have 1 'u,v' data to align a single texture (altho you can have multiple textures. BUT each texture will only have 1 set of u'v data per vertex). For a classic example try to texture map a cube that has 8 vertices. It will look terrible!! But when you create the cube with each face having its own 4 vertices then the texture mapping will work properly.

Good luck
Regards


PaulJG(Posted 2005) [#12]
Ok, thanks Mark - think I'm on the right track.. ???

My book here says that to use the glNormalPointer command the array should contain normal vectors for each vertex.

Now is that normals for the vertex mesh (ie. 20 of em) ?, or for each point on the face as its read through ? (so I'd need 12*3) normals.

In other words, is it referencing them from the vertex data, or going through them 3 at a time, like the face list. In which case I'd just have to write a new normal array that contains all the normal coords needed for each point of each face as needed.

Beer in the post for anyone that can understand any of the above !. ;)

Col, since my engine is based on tris, each side would have 6 points ?? - so 36 vertexes needed it total ?


PaulJG(Posted 2005) [#13]
Finally.. I think I've got it - I have to go through the face list - make a new vertex list, based on the vertexes that the sides used.

Create a new face list which should join the 3 new points I've just created.

Create a normals map for the 3 new vertexes I've just made by referencing the normals map for the face index.

Repeat until the whole face list is done.

So I'll end up with 36 vertexes and normals !!!.

Hell of a lot of numbers that will need processing just for a cube.. 36 * 3 (x,y,z) = 108 !


col(Posted 2005) [#14]
Heres a snippet from a program i learned about vertex arrays and the 'flexible vertex format'. Its called 'flexible vertex format' beause there are various ways you can describe the vertex data. Here each line of data will be desribing one single vertex and will be read as:

Texturecoord U, Texturecood V, VertexX, VertexY, VertexZ

Global cubeVertices:Float[] = [0.0,1.0, -1.0,-1.0, 1.0,..
1.0,1.0, 1.0,-1.0, 1.0,..
1.0,0.0, 1.0, 1.0, 1.0,..
0.0,0.0, -1.0, 1.0, 1.0,..
..
0.0,0.0, -1.0,-1.0,-1.0,..
0.0,1.0, -1.0, 1.0,-1.0,..
1.0,1.0, 1.0, 1.0,-1.0,..
1.0,0.0, 1.0,-1.0,-1.0,..
..
1.0,1.0, -1.0, 1.0,-1.0,..
1.0,0.0, -1.0, 1.0, 1.0,..
0.0,0.0, 1.0, 1.0, 1.0,..
0.0,1.0, 1.0, 1.0,-1.0,..
..
1.0,0.0, -1.0,-1.0,-1.0,..
0.0,0.0, 1.0,-1.0,-1.0,..
0.0,1.0, 1.0,-1.0, 1.0,..
1.0,1.0, -1.0,-1.0, 1.0,..
..
0.0,0.0, 1.0,-1.0,-1.0,..
0.0,1.0, 1.0, 1.0,-1.0,..
1.0,1.0, 1.0, 1.0, 1.0,..
1.0,0.0, 1.0,-1.0, 1.0,..
..
0.0,1.0, -1.0,-1.0,-1.0,..
1.0,1.0, -1.0,-1.0, 1.0,..
1.0,0.0, -1.0, 1.0, 1.0,..
0.0,0.0, -1.0, 1.0,-1.0]

This is known as an 'Interleaved Array' because the array doesn't conatain just the vertex position data,but the texture uv data as well, all in the same array. This isn't a problem because when it comes to rendering, you tell opengl the format in which the data is stored:

glBindTexture( GL_TEXTURE_2D, texture )
glInterleavedArrays( GL_T2F_V3F, 0, cubeVertices )
glDrawArrays( GL_QUADS, 0, 24 )

Notice the sencond line. It has GL_T2F_V3F .This will tell opengl that the data is stored as 'T2F' meaning 2 FLOAT textute coords and 'V3F' meaing 3 FLOAT vertex coords. I know they are quads and are in the array as quads, but this priciple is the same for triangles.

Heres another program showing you how you can use indexed interleaved arrays:



Here is another program using a similar method to what you are using ( multiiple arrays )



I know the programs are using quads, but its the theory that matters.
Also all you would do is create an empty array and load the .x file into the array, filling the arrays with the correct data. What really needs to happen is you have to decide and create your own model format in your program and load any external files ( ie .x, or 3ds ) and convert them to your model format ready for rendering. This will also make it really easy to expand the number of file formats you can support. To support another model format, you only need to write a parser and convert it to your model format.


PaulJG(Posted 2005) [#15]
Done it !!! - Finally !! :)

Many thanks Col for sticking it through to the end with me.

I was gonna use interweaved arrays, like your example - but I think it could restrict things a little. At least with seperate arrays, I can change the pointers to new array information if needed.


col(Posted 2005) [#16]
Well done.

Looking forward to seeing what you're creating.... :)