Tutorials:3ds Loader - Spacesimulator.net

来源:百度文库 编辑:神马文学网 时间:2024/10/04 20:35:07
Contents
[]
1 INTRODUCTION
2 THE 3DS FILE STRUCTURE
3 A SHORT BRIEFING
4 FINALLY... CODE!
5 CONCLUSIONS
6 LINKS
INTRODUCTION
It's now time to say goodbye to our dear cube! In this lesson we will develop a routine to load 3ds objects,a very popular file format on the internet and supported by various 3dmodelers. A 3d modeler allows you to create any type of object in a moreintuitive and human way rather than to define by hand the coordinatesof the vertices, which can become an impossible task even for simpleobjects just slightly more complicated than a cube.
Actually, I am very reluctant to throw away the cube, such asimple and perfect figure. However, until proven otherwise, spaceships,planets, missiles and anything that has to do with a space simulatorseems to be completely different from the cube.
Before starting to write the code it will be necessary to analyzethe 3ds file structure. Ok, prepare your favorite programming drink andget ready...
THE 3DS FILE STRUCTURE
A 3ds file contains a series of information used to describe everydetail of a 3d scene composed of one or more objects. A 3ds filecontains a series of blocks called Chunks. What is contained in these blocks? Everything necessary to describe the scene: the name of each object, the vertices coordinates, the mapping coordinates, the list of polygons, the faces colors, the animation keyframes and so on.
These chunks don't have a linear structure. This means that some chunks are dependent on others and can only be read if their relative parent chunks have been read first. It's not necessary to read all the chunks and we will only consider the most important ones here.
I will base my description of the 3ds file format on the information contained in the 3dsinfo.txt file written by Jochen Wilhelmy which explains in detail the structure of all the chunks.
A chunk is composed of 3 fields:
Identifier: a hexadecimal number two bytes in length that identifies the chunk. This information immediately tells us if the chunk is useful for our purpose. If we need the chunk we can then extrapolate the scene information in it and, if necessary, any child chunks it may have. If we don't need the chunk, we jump it using the following information:
Length of the chunk: a 4 byte number that is the sum of the chunk length and all the lengths of every contained sub-chunk.
Chunk data: this field has a variable length and conatians all the data for the scene.
This table shows the offset (in bytes) and the length (also in bytes) of each field in a typical chunk:
OffsetLengthDescription
0 2 Chunk identifier
2 4 Chunk length: chunk data + sub-chunks(6+n+m)
6 n Data
6+n m Sub-chunks
We can see from the last line in the table exactly how some chunksare dependent on others: each child chunk is in fact contained insidethe field "Sub-chunks" of the parent chunk.
The following are the most important chunks in a 3ds file. Please note the hierarchy among the various elements:
MAIN CHUNK 0x4D4D
3D EDITOR CHUNK 0x3D3D
OBJECT BLOCK 0x4000
TRIANGULAR MESH 0x4100
VERTICES LIST 0x4110
FACES DESCRIPTION 0x4120
FACES MATERIAL 0x4130
MAPPING COORDINATES LIST 0x4140
SMOOTHING GROUP LIST 0x4150
LOCAL COORDINATES SYSTEM 0x4160
LIGHT 0x4600
SPOTLIGHT 0x4610
CAMERA 0x4700
MATERIAL BLOCK 0xAFFF
MATERIAL NAME 0xA000
AMBIENT COLOR 0xA010
DIFFUSE COLOR 0xA020
SPECULAR COLOR 0xA030
TEXTURE MAP 1 0xA200
BUMP MAP 0xA230
REFLECTION MAP 0xA220
[SUB CHUNKS FOR EACH MAP]
MAPPING FILENAME 0xA300
MAPPING PARAMETERS 0xA351
KEYFRAMER CHUNK 0xB000
MESH INFORMATION BLOCK 0xB002
SPOT LIGHT INFORMATION BLOCK 0xB007
FRAMES (START AND END) 0xB008
OBJECT NAME 0xB010
OBJECT PIVOT POINT 0xB013
POSITION TRACK 0xB020
ROTATION TRACK 0xB021
SCALE TRACK 0xB022
HIERARCHY POSITION 0xB030
As mentioned earlier, if we want to read a particular chunk we must always read its parent chunk first. Imagine the 3ds file is a tree and the chunk that we need is a leaf(and we are a little ant on the ground). In order to reach the leaf, weneed to start from the trunk and cross any branches that lead to thatleaf. For example, if we want to reach the chunk VERTICES LIST, we have to read the MAIN CHUNK first, then the 3D EDITOR CHUNK, the OBJECT BLOCK and finally the TRIANGULAR MESH chunk. The other chunks can safely be skipped.
Now let's prune our tree and leave only the branches we are going to use in this tutorial : vertices, faces, mapping coordinates and their relative parents:
MAIN CHUNK 0x4D4D
3D EDITOR CHUNK 0x3D3D
OBJECT BLOCK 0x4000
TRIANGULAR MESH 0x4100
VERTICES LIST 0x4110
FACES DESCRIPTION 0x4120
MAPPING COORDINATES LIST 0x4140
Here are the chunks described in detail:
MAIN CHUNK
Identifier 0x4d4d
Length 0 + sub-chunks length
Chunk father None
Sub chunks 3D EDITOR CHUNK
Data None
3D EDITOR CHUNK
Identifier 0x3D3D
Length 0 + sub-chunks length
Chunk father MAIN CHUNK
Sub chunks OBJECT BLOCK, MATERIAL BLOCK, KEYFRAMER CHUNK
Data None
OBJECT BLOCK
Identifier 0x4000
Length Object name length + sub-chunks length
Chunk father 3D EDITOR CHUNK
Sub chunks TRIANGULAR MESH, LIGHT, CAMERA
Data Object name
TRIANGULAR MESH
Identifier 0x4100
Length 0 + sub-chunks length
Chunk father OBJECT BLOCK
Sub chunks VERTICES LIST, FACES DESCRIPTION, MAPPING COORDINATES LIST
Data None
VERTICES LIST
Identifier 0x4110
Length varying + sub-chunks length
Chunk father TRIANGULAR MESH
Sub chunks None
Data Vertices number (unsigned short)
Vertices list: x1,y1,z1,x2,y2,z2 etc. (for each vertex: 3*float)
FACES DESCRIPTION
Identifier 0x4120
Length varying + sub-chunks length
Chunk father TRIANGULAR MESH
Sub chunks FACES MATERIAL
Data Polygons number (unsigned short)
Polygons list: a1,b1,c1,a2,b2,c2 etc. (for each point: 3*unsigned short)
Face flag: face options, sides visibility etc. (unsigned short)
MAPPING COORDINATES LIST
Identifier 0x4140
Length varying + sub-chunks length
Chunk father TRIANGULAR MESH
Sub chunks SMOOTHING GROUP LIST
Data Vertices number (unsigned short)
Mapping coordinates list: u1,v1,u2,v2 etc. (for each vertex: 2*float)
Now that the 3ds file format is clear enough, we are going to take alook at the code for this tutorial. What? You're completely lost? =DLet's continue anyway. The chunks structure will become clearer to youas you go through the lesson. After all, we are programmers and weunderstand C better than own chatter ;)
A SHORT BRIEFING
The steps we need to take in order to load a 3ds object and save it in the format defined by our engine are:
implement a "while" loop (as we did for the texture loader) that continues its execution until the end of file is reached.
read the chunk_id and the chunk_length each iteration of the loop.
analyze the content of the chunk_id using a switch .
if the chunk is a section of the tree we don't need to read, we jump the whole length of that chunk by moving the file pointer to a new position which is calculated by using the length of the current chunk added to the current position. This allows us to jump any chunk we don't need as well as all contained sub-chunks. In other words: let's jump to another branch! Are you starting to feel like a monkey yet? =)
if the chunk allows us to reach another chunk that we need, or it contains data that we need, then we read its data if needed, and then move to the next chunk.
FINALLY... CODE!
The first thing to do is to create the files that will contain the new routines.
We have used the file tutorial(n).cpp to contain the maindata types of the engine in the previous tutotials. However, since ourdata structures are becoming bigger, we will insert the declarations ofthe data types in a header file that we will call tutorial4.h
First, we increase the number of vertices and polygons that our engine is able to manage.
#define MAX_VERTICES 8000
#define MAX_POLYGONS 8000
Next, we add the field char name[20]; to the structure obj_type. This field will contain the name of the loaded object.
Lastly, we modify the name of our object variable from obj_type cube; to obj_type object; just to "highlight" the generic nature of our object.
The next file to create is 3dsloader.cpp. In this file, we insert the following routine:
char Load3DS (obj_type_ptr p_object, char *p_filename)
{
int i;
FILE *l_file;
unsigned short l_chunk_id;
unsigned int l_chunk_length;
unsigned char l_char;
unsigned short l_qty;
unsigned short l_face_flags;
The Load3DS routine accepts two parameters: a pointer to the objectdata structure and the name of the file to open. It returns "0" if thefile has not been found or "1" if the file has been found and read.There aren't too many variables to initialize: we have the usual counteri, a pointer to the file *l_file and a support variable to extrapolate byte data l_char.The other variables are:
unsigned short l_chunk_id; a 2 bytes hexadecimal number that tells us the chunk's id.
unsigned int l_chunk_length; a 4 bytes number used to specify the length of the chunk.
unsigned short l_qty; a support variable that will tell us the quantity of information to read.
unsigned short l_face_flags; This variable holds various information regarding the current polygon (visible, not visible etc.) which the 3d editor uses to render the scene. We will only use this value to move the file pointer to the next chunk position.
So let's open the file at last!
if ((l_file=fopen (p_filename, "rb"))== NULL) return 0; //Open the file
while (ftell (l_file) < filelength (fileno (l_file))) //Loop to scan the whole file
{
The while loop is performed for the entire length of the file. The ftell function allows us to acquire the current file pointer position while filelength returns the length of the file.
fread (&l_chunk_id, 2, 1, l_file); //Read the chunk header
fread (&l_chunk_length, 4, 1, l_file); //Read the length of the chunk
Here, we have extrapolated the identifier and the length of the chunk and have saved them in l_chunk_id and l_chunk_length respectively.First, we analyze the content of l_chunk_id:
switch (l_chunk_id)
{
case 0x4d4d:
break;
We have found the MAIN CHUNK! Cool! What are we going to dowith it? Simple... nothing! In fact, the MAIN CHUNK has no data.However, we are interested in its sub-chunks. We have included thisparticular "case" statement so that the whole MAIN chunk is notjumped! Jumping the length of the MAIN CHUNK would have meant moving thefile pointer to the end of the file due to the "default case" at theend of this switch statement. I will discuss this "default case" more,later in this tutorial
We take the same approach for the 3D EDITOR CHUNK:
case 0x3d3d:
break;
This is the next node that we need to navigate through in order toreach the information we need. Once again, this node has no data. Solet's pretend to read it =) This will bring us to the child called Object Block:
case 0x4000:
i=0;
do
{
fread (&l_char, 1, 1, l_file);
p_object->name[i]=l_char;
i++;
}while(l_char != '\0' && i<20);
break;
The chunk OBJECT BLOCK finally has some interesting information: the name of the object. We store this data in the "name" field of the objectstructure. The while loop exits if the '\0' character is encountered orthe number of characters exceeds 20. Be careful! We have just read allthe data of this chunk and this has moved the file pointer to the nextchunk:
case 0x4100:
break;
This last chunk is simply another empty node that is the parent node of the next chunks that we must read.
Finally, here are the vertices! The chunk VERTICES LIST contains all the vertices of the model:
case 0x4110:
fread (&l_qty, sizeof (unsigned short), 1, l_file);
p_object->vertices_qty = l_qty;
printf("Number of vertices: %d\n",l_qty);
for (i=0; i{
fread (&p_object->vertex[i].x, sizeof(float), 1, l_file);
fread (&p_object->vertex[i].y, sizeof(float), 1, l_file);
fread (&p_object->vertex[i].z, sizeof(float), 1, l_file);
}
break;
First, we read the value "quantity" and use it to create a forloop that reads all the vertices. We then save each vertex in thecorresponding field of the object structure.
The chunk FACES DESCRIPTION contains a list of the object's polygons:
case 0x4120:
fread (&l_qty, sizeof (unsigned short), 1, l_file);
p_object->polygons_qty = l_qty;
printf("Number of polygons: %d\n",l_qty);
for (i=0; i{
fread (&p_object->polygon[i].a, sizeof (unsigned short), 1, l_file);
fread (&p_object->polygon[i].b, sizeof (unsigned short), 1, l_file);
fread (&p_object->polygon[i].c, sizeof (unsigned short), 1, l_file);
fread (&l_face_flags, sizeof (unsigned short), 1, l_file);
}
break;
As explained in tutorial 1, the structure dealing with polygonsdoesn't contain coordinates, only numbers that correspond to elementscontaining a list of vertices. In order to read this chunk we do exactlythe same procedure we have done for the vertices chunk: first, we readthe number of faces then we create a for loop to read all the faces.Each face also has another 2 byte field, the face flags, thatcontains some information useful only for 3d editors (indicating visiblefaces and so on). We will only read it to move the file pointer to thenext chunk.
Finally, we read the MAPPING COORDINATES LIST:
case 0x4140:
fread (&l_qty, sizeof (unsigned short), 1, l_file);
for (i=0; i{
fread (&p_object->mapcoord[i].u, sizeof (float), 1, l_file);
fread (&p_object->mapcoord[i].v, sizeof (float), 1, l_file);
}
break;
Once again, we read the quantity and use this value to set up a for loop. Each point has two coordinates, u and v do you remember? No?? What are you doing here then? ;P
Finally, the default case:
default:
fseek(l_file, l_chunk_length-6, SEEK_CUR);
}
}
This means that we are at the end of the routine. This case is simple: when we find chunks that we don't want to read, the fseek function moves the file pointer to the beginning of the next chunk using the chunk_length information
We have finished! Very little remains to be done. We close the file and return 1.
fclose (l_file); // Closes the file stream
return (1); // Returns ok
}
CONCLUSIONS
The 3ds reader that we have developed here is a starting point formore complex readers. Keep in mind however that our routine can onlyread a 3ds file if there is only one object present and it is positionedat the center. One of the next tutorials (the matrices tutorial), willadd the functionality needed to load other objects. This will be thefun part. We have to include other spaceships right? Otherwise we won'thave anything to destroy =)
This lesson wasn't so hard was it? After all, we have alreadydone the big work in previous lessons. We can use all the code writtenso far for the next tutorial, in which we will learn how to add lightingusing OpenGL functions. Bye bye for now happy coders!
LINKS
The Source Code of this lesson can be downloaded from theTutorials Main Page
Please use theForum to post any kind of proposal, question, bugs report or whatever else.
To collaborate to this project please read theCollaboration page.
Go back to Tutorials Main Page