3DS Part 2- "the journey continues" - More Chunks
by bkenwright@xbdev.net
Some simple changes from tutorial part -1-. I'm going to use "int
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR,
int)" instead of "void main()" for the entry point....as its the
windows entry point...and later on I'll be doing simple simple directx routines
to output our 3d data. As its one thing to have the 3d data...but if I
display it as well with a few simple directx routines it makes things a little
easy to swallow.
There first thing I do is build from the other tutorial by adding adding some
keyframes to our box.3ds file - e.g animating it. So we could see that the
keyframe chunks are in the file. I do this by adding in a "void
ReadKEYF3DS(FILE *fp, stChunk* Chunk)" function, which dumps the main keyframe
chunks out to our text file.
Our wonderful code:
// Our .3DS file that we'll use for testing.
#define
FILENAME "cube.3ds"
#include
<windows.h>
#include
<stdio.h>
// I've put the chunk defenitions in a seperate file on there own, called
// chunkdetails.h, which we'll include here.
#include
"chunkdetails.h"
/***************************************************************************/
/*
*/
/* Write feedback information to a file - so we can understand what is
*/
/* really happening inside that .3ds file.
*/
/*
*/
/***************************************************************************/
void
abc(char *s)
{
FILE *fp;
fp
= fopen("t.txt", "a+");
fprintf(fp, "%s", s);
fclose(fp);
}
/***************************************************************************/
/*
*/
/* Some user functions to make the reading of the .3ds file easier
*/
/*
*/
/***************************************************************************/
struct
stChunk
{
unsigned short
ID;
unsigned int
length;
unsigned int
bytesRead;
};
void
ReadChunk(FILE*fp, stChunk *pChunk)
{
unsigned short
ID = 0;
unsigned int
bytesRead = 0;
unsigned int
bChunkLength = 0;
bytesRead = fread(&ID, 1, 2, fp);
bytesRead += fread(&bChunkLength, 1, 4, fp);
pChunk->ID = ID;
pChunk->length = bChunkLength;
pChunk->bytesRead = bytesRead;
}
void
SkipChunk(FILE*fp, stChunk *pChunk)
{
int buffer[50000] = {0};
fread(buffer, 1, pChunk->length - pChunk->bytesRead, fp);
}
void
DisplayChunkInfo(stChunk* pChunk)
{
char buf[1000] = {0};
sprintf(buf, "Chunk ID: 0x %04x Size of Chunk: %u\n", pChunk->ID, pChunk->length);
abc(buf);
}
/***************************************************************************/
/*
*/
/* Now we are going to read in all the branches
*/
/*
*/
/***************************************************************************/
void
ReadEDIT3DS(FILE *fp, stChunk* Chunk)
{
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
void
ReadKEYF3DS(FILE *fp, stChunk* Chunk)
{
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
/***************************************************************************/
/*
*/
/* Read in .3ds file.
*/
/*
*/
/***************************************************************************/
void
read3ds()
{
FILE* pFile;
pFile = fopen(FILENAME, "rb");
stChunk Chunk = {0};
ReadChunk(pFile, &Chunk);
DisplayChunkInfo(&Chunk);
while(Chunk.bytesRead < Chunk.length)
{
stChunk tempChunk = {0};
ReadChunk(pFile, &tempChunk);
DisplayChunkInfo(&tempChunk);
unsigned
int bytesRead = 0;
switch( tempChunk.ID)
{
case EDIT3DS:
ReadEDIT3DS(pFile, &tempChunk);
bytesRead = tempChunk.length;
break;
case KEYF3DS:
ReadKEYF3DS(pFile, &tempChunk);
bytesRead = tempChunk.length;
break;
default:
SkipChunk(pFile, &tempChunk);
bytesRead = tempChunk.length;
}
Chunk.bytesRead += bytesRead;
}
fclose(pFile);
}
/***************************************************************************/
/*
*/
/* The program entry point, this is where we start and end.
*/
/*
*/
/***************************************************************************/
int
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR,
int)
{
read3ds();
return 0;
} |
Output file details:
Chunk ID: 0x 4d4d Size of Chunk: 595
Chunk ID: 0x 0002 Size of Chunk: 10
Chunk ID: 0x 3d3d Size of Chunk: 360
Chunk ID: 0x 3d3e Size of Chunk: 10
Chunk ID: 0x 0100
Size of Chunk: 10
Chunk ID: 0x 4000
Size of Chunk: 334
Chunk ID: 0x b000 Size of Chunk: 219
Chunk ID: 0x b00a
Size of Chunk: 21
Chunk ID: 0x b008
Size of Chunk: 14
Chunk ID: 0x b009
Size of Chunk: 10
Chunk ID: 0x b002
Size of Chunk: 168 |
As before...a quick review:
4d4d |
3DS Identification |
0002 |
File Version |
3d3d |
Our 3D File Information Header - This is
where our 3D meshes/objects would be |
0100 |
Scale Factor |
4000 |
Our 3D Object - as a 3d file can contain
more than one 3d object...with names and world positions etc. I
could have to seperate 0x4000 which would give me the vertices, name etc of
two different 3D objects ...circle and a square for example. |
b000 |
Start of our KeyFrame (animation)
information |
b00a |
Viewport layout etc - not really needed
here |
b008 |
Keyframe segment details (start and end) |
b009 |
Start Time - again not really needed most
of the time |
b002 |
As you can see by the size - this is where
our informatino is kept, node, pivot, keyframe positions etc. |
Well it was worth explaining some of it again...even though a lot of the
keyframe animation data is usually not needed...but we'll get to animation
eventually. As you could animate a 3D scene save it as a .3ds file and
open it with our very own loader program - great for demo's etc.
Now for our next trick...we'll actually go so far as extract our
vertices...yup those x,y,z things that make up our 3D Shape (our cube in this
case). So we'll get the name of our object and its details and output them
to our file.
Using "void
ReadNAMED_OBJECT(FILE *fp, stChunk* Chunk)" function we are going to read
in the objects and dump some details out, like the number of vertices, its
shape's name, number of sides etc. As for each 'Object'...now remember
that...as for this tutorial code I've only got a single cube...but if you have 2
cubes in your 3DS file...this function would get called twice.
Staight after entering the 0x4000 chunk...I mean the "void
ReadNAMED_OBJECT(..)" function, a null terminated string follows, that
contains the shapes name, and can be any length, but finishes with a null
indicating the end of it.
After the string name, where back to chunks again...which our nested vertice
count and face count etc.
/***************************************************************************/
/*
*/
/* Read in the actual object data
*/
/*
*/
/***************************************************************************/
void
ReadVerticesMESH_FACES(FILE *fp, stChunk* Chunk)
{
unsigned int
iNumberFaces = 0; //= Chunk->length - 6;
Chunk->bytesRead += fread(&iNumberFaces, 1, 2, fp);
char buff[100];
sprintf(buff, "Number of Faces %u\n", iNumberFaces);
abc(buff);
SkipChunk(fp, Chunk);
}
void
ReadVerticesMESH_VERTICES(FILE *fp, stChunk* Chunk)
{
unsigned int
iNumberVertices = 0;
Chunk->bytesRead += fread(&iNumberVertices, 1, 2, fp);
// e.g. x , y, z
char buff[100];
sprintf(buff, "Number of Vertices %d\n", iNumberVertices);
abc(buff);
SkipChunk(fp, Chunk);
}
void
LoadMeshOBJ_MESH(FILE *fp, stChunk* Chunk)
{
abc("\n");
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
case MESH_VERTICES:
ReadVerticesMESH_VERTICES(fp, &tempChunk);
break;
case MESH_FACES:
ReadVerticesMESH_FACES(fp, &tempChunk);
break;
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
/***************************************************************************/
/*
*/
/* Get the object data
*/
/*
*/
/***************************************************************************/
int
GetString(FILE *fp)
{
int index = 0;
char buffer[100] = {0};
fread(buffer, 1, 1, fp);
while( *(buffer + index++) != 0)
{
fread(buffer + index, 1, 1, fp);
}
abc(buffer);
abc("\n");
return (strlen(buffer) + 1);
}
/***************************************************************************/
//0x4000
// This is our 3D object, a cube etc. We can have numberous of these in our
// 3DS file, a cube, sphere, etc...
void
ReadNAMED_OBJECT(FILE *fp, stChunk* Chunk)
{
abc("\n");
// The strange thing is, the next few parts of
this chunk represent
// the name of the object. Then we start chunking
again.
unsigned int
characterlen = GetString(fp);
Chunk->bytesRead += characterlen;
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
case OBJ_MESH:
LoadMeshOBJ_MESH(fp, &tempChunk);
break;
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
/***************************************************************************/
/*
*/
/* Now we are going to read in all the branches
*/
/*
*/
/***************************************************************************/
void
ReadEDIT3DS(FILE *fp, stChunk* Chunk)
//0x3D3D
{
abc("\n");
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
case NAMED_OBJECT:
ReadNAMED_OBJECT(fp, &tempChunk);
break;
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
} |
Below is our text file output - I've added a bit of colour to the below file,
so you can see where nearly there. There is light at the end of our
journey after all. You can now see our 3d object details.
Chunk ID: 0x 4d4d Size of Chunk: 595
Chunk ID: 0x 0002 Size of Chunk: 10
Chunk ID: 0x 3d3d Size of Chunk: 360
Chunk ID: 0x 3d3e Size of Chunk: 10
Chunk ID: 0x 0100 Size of Chunk: 10
Chunk ID: 0x 4000 Size of Chunk: 334
Box01
Chunk ID: 0x 4100 Size of
Chunk: 322
Chunk ID: 0x 4110 Size of
Chunk: 104
Number of
Vertices 8
Chunk ID: 0x 4160 Size of
Chunk: 54
Chunk ID: 0x 4120 Size of
Chunk: 158
Number of
Faces 12
Chunk ID: 0x b000 Size of Chunk: 219 |
Dumping Our Shape x,y,z
Now when We read our data in....We have Faces AND Vertices seperate.
With nearly all 3D file formats, this is how data is stored. You better
get used to it, so face it now, as the Face data is an array of index's into our
vertex data, where each value in the array is a face....??? ...yup...well a face
is a triangle. 3 vertice points make up a single triangle. So if we
had the values [0,1,2] for our first face value, that would be, triangle face
uses vertices 0, 1, 2 from our vertice data we read in..simple??..huh?...well it
becomes more clearer the more you think about it...and the more you play with it
:)
The next code dumps our 3D file data out...the actual data! So you can
see the x,y,z values, and there corresponding face index values. I've only
changed a few lines of the code...added some new lines in to read in our data
and dump it to our file, so I'll not display the whole .c file again.
...code....
/***************************************************************************/
/*
*/
/* Read in the actual object data
*/
/*
*/
/***************************************************************************/
// 0x4120
void
ReadVertRefsMESH_FACES(FILE *fp, stChunk* Chunk)
{
unsigned int
iNumberFaces = 0; //= Chunk->length - 6;
Chunk->bytesRead += fread(&iNumberFaces, 1, 2, fp);
char buff[100];
sprintf(buff, "Number of Faces %u\n", iNumberFaces);
abc(buff);
// Each face is 3 points A TRIANGLE!..WOW
struct stFace{
unsigned short p1, p2, p3; };
stFace *pFaces = new stFace[iNumberFaces];
Chunk->bytesRead += fread(pFaces, 1, iNumberFaces*sizeof(stFace),
fp);
for( int i=0;
i<iNumberFaces; i++ )
{
sprintf(buff, "\t Face: Index1: %d, Index2: %d, Index3 %d\n",
pFaces[i].p1, pFaces[i].p2,
pFaces[i].p3);
abc(buff);
}
delete pFaces;
SkipChunk(fp, Chunk);
}
// 4110
void
ReadVerticesMESH_VERTICES(FILE *fp, stChunk* Chunk)
{
unsigned int
iNumberVertices = 0;
Chunk->bytesRead += fread(&iNumberVertices, 1, 2, fp);
// e.g. 8 Vertices make up our simple box!
char buff[300];
sprintf(buff, "Number of Vertices %d\n", iNumberVertices);
abc(buff);
// Allocate Memory and dump our vertices to the
screen.
struct stVect{
float x, y, z; };
stVect *pVerts = new stVect[iNumberVertices];
Chunk->bytesRead += fread( (void*)pVerts, 1,
iNumberVertices*sizeof(stVect), fp);
for(int i=0;
i<iNumberVertices; i++)
{
sprintf(buff, "\t Vertices Point: x: %.2f, \ty: %.2f, \tz:
%.2f\n",
pVerts[i].x,
pVerts[i].y, pVerts[i].z);
abc(buff);
}
delete[] pVerts;
SkipChunk(fp, Chunk);
}
//0x4100
void
LoadMeshOBJ_MESH(FILE *fp, stChunk* Chunk)
{
abc("\n");
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
case MESH_VERTICES:
ReadVerticesMESH_VERTICES(fp, &tempChunk);
break;
case MESH_FACES:
ReadVertRefsMESH_FACES(fp, &tempChunk);
break;
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
/***************************************************************************/
/*
*/
/* Get the object data
*/
/*
*/
/***************************************************************************/
int
GetString(FILE *fp)
{
int index = 0;
char buffer[100] = {0};
fread(buffer, 1, 1, fp);
while( *(buffer + index++) != 0)
{
fread(buffer + index, 1, 1, fp);
}
abc(buffer);
abc("\n");
return (strlen(buffer) + 1);
}
/***************************************************************************/
//0x4000
// This is our 3D object, a cube etc. We can have numberous of these in our
// 3DS file, a cube, sphere, etc...
void
ReadNAMED_OBJECT(FILE *fp, stChunk* Chunk)
{
abc("\n");
// The strange thing is, the next few parts of
this chunk represent
// the name of the object. Then we start chunking
again.
unsigned int
characterlen = GetString(fp);
Chunk->bytesRead += characterlen;
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
switch( tempChunk.ID)
{
case OBJ_MESH:
LoadMeshOBJ_MESH(fp, &tempChunk);
break;
default:
SkipChunk(fp, &tempChunk);
}
Chunk->bytesRead += tempChunk.length;
}
}
etc etc .....code..... |
Well we run our little program up...and we get our vertices...finally ...I
thought it would never come....but below shows our text file and as you can see
we have our box name, and the vertice's and there face references.
Output file:
Chunk ID: 0x 4d4d Size of Chunk: 595
Chunk ID: 0x 0002 Size of Chunk: 10
Chunk ID: 0x 3d3d Size of Chunk: 360
Chunk ID: 0x 3d3e Size of Chunk: 10
Chunk ID: 0x 0100 Size of Chunk: 10
Chunk ID: 0x 4000 Size of
Chunk: 334
Box01
Chunk ID: 0x 4100 Size of
Chunk: 322
Chunk ID: 0x 4110 Size of
Chunk: 104
Number of Vertices 8
Vertices Point:
x: -4.93, y: -22.27, z: 0.00
Vertices Point:
x: 7.16, y: -22.27, z: 0.00
Vertices Point:
x: -4.93, y: 8.38, z: 0.00
Vertices Point:
x: 7.16, y: 8.38, z: 0.00
Vertices Point:
x: -4.93, y: -22.27, z: 16.17
Vertices Point:
x: 7.16, y: -22.27, z: 16.17
Vertices Point:
x: -4.93, y: 8.38, z: 16.17
Vertices Point:
x: 7.16, y: 8.38, z: 16.17
Chunk ID: 0x 4160 Size of
Chunk: 54
Chunk ID: 0x 4120 Size of
Chunk: 158
Number of Faces 12
Face: Index1:
0, Index2: 2, Index3 3
Face: Index1:
6, Index2: 3, Index3 1
Face: Index1:
0, Index2: 6, Index3 4
Face: Index1:
5, Index2: 7, Index3 6
Face: Index1:
7, Index2: 6, Index3 4
Face: Index1:
6, Index2: 0, Index3 1
Face: Index1:
5, Index2: 6, Index3 5
Face: Index1:
4, Index2: 0, Index3 6
Face: Index1:
1, Index2: 3, Index3 7
Face: Index1:
6, Index2: 7, Index3 5
Face: Index1:
1, Index2: 6, Index3 3
Face: Index1:
2, Index2: 6, Index3 6
Chunk ID:
0x b000 Size of Chunk: 219 |
...Now to show you that this work even more...I did another .3ds file, one
with a cube, and a sphere and some other things...saved it and loaded it into
our simple program above.
Note, I colour coded the below html text so you could in effect take a quick
glance at it and see the main parts easily...as it uses the "multishape.3ds"
file instead of the "cube.3ds" file. As I put a Sphere and a Cone in there
file as well so you could see whats in there. I've commented some of the
code out, so it doens't take up pages of recursive numbers..
Chunk ID: 0x 4d4d Size of Chunk: 4769
Chunk ID: 0x 0002 Size of Chunk: 10
Chunk ID: 0x 3d3d Size of Chunk: 4197
Chunk ID: 0x 3d3e Size of Chunk: 10
Chunk ID: 0x 0100 Size of Chunk: 10
Chunk ID: 0x 4000 Size of
Chunk: 332
Box
Chunk ID: 0x 4100 Size of
Chunk: 322
Chunk ID: 0x 4110 Size of
Chunk: 104
Number of Vertices 8
Vertices Point: x:
0.40, y: 0.00, z: -6.36
Vertices Point: x:
5.40, y: 0.00, z: -6.36
Vertices Point: x:
0.40, y: 0.00, z: -1.36
Vertices Point: x:
5.40, y: 0.00, z: -1.36
Vertices Point: x:
0.40, y: -5.00, z: -6.36
Vertices Point: x:
5.40, y: -5.00, z: -6.36
Vertices Point: x:
0.40, y: -5.00, z: -1.36
Vertices Point: x:
5.40, y: -5.00, z: -1.36
Chunk ID: 0x 4160 Size of
Chunk: 54
Chunk ID: 0x 4120 Size of
Chunk: 158
Number of Faces 12
Face: Index1: 0,
Index2: 2, Index3 3
Face: Index1: 6,
Index2: 3, Index3 1
Face: Index1: 0,
Index2: 6, Index3 4
Face: Index1: 5,
Index2: 7, Index3 6
Face: Index1: 7,
Index2: 6, Index3 4
Face: Index1: 6,
Index2: 0, Index3 1
Face: Index1: 5,
Index2: 6, Index3 5
Face: Index1: 4,
Index2: 0, Index3 6
Face: Index1: 1,
Index2: 3, Index3 7
Face: Index1: 6,
Index2: 7, Index3 5
Face: Index1: 1,
Index2: 6, Index3 3
Face: Index1: 2,
Index2: 6, Index3 6
Chunk ID: 0x 4000 Size of
Chunk: 3362
GeoSphere
Chunk ID: 0x 4100 Size of
Chunk: 3346
Chunk ID: 0x 4110 Size of
Chunk: 1112
Number of Vertices 92
Vertices Point: x:
-7.81, y: 18.71, z: 6.00
Vertices Point: x:
-2.44, y: 18.71, z: 2.68
Vertices Point: x:
-6.15, y: 23.81, z: 2.68
Vertices Point: x:
-12.15, y: 21.86, z: 2.68
Vertices Point: x:
-12.15, y: 15.55, z: 2.68
//******cut out to keep
short***********************//
Vertices Point: x:
-3.02, y: 15.23, z: 1.02
Vertices Point: x:
-6.71, y: 22.09, z: -4.83
Vertices Point: x:
-10.68, y: 20.80, z: -4.83
Vertices Point: x:
-10.68, y: 16.62, z: -4.83
Vertices Point: x:
-6.71, y: 15.32, z: -4.83
Vertices Point: x:
-4.25, y: 18.71, z: -4.83
Chunk ID: 0x 4160 Size of
Chunk: 54
Chunk ID: 0x 4120 Size of
Chunk: 2174
Number of Faces 180
Face: Index1: 0,
Index2: 12, Index3 14
Face: Index1: 7,
Index2: 12, Index3 13
Face: Index1: 72,
Index2: 7, Index3 12
//******cut out to
keep short***********************//
Face: Index1: 86,
Index2: 31, Index3 30
Face: Index1: 7,
Index2: 86, Index3 30
Face: Index1: 40,
Index2: 7, Index3 40
Face: Index1: 30,
Index2: 5, Index3 7
Chunk ID: 0x 4000 Size of
Chunk: 477
Cone
Chunk ID: 0x 4100 Size of
Chunk: 466
Chunk ID: 0x 4110 Size of
Chunk: 152
Number of Vertices 12
Vertices Point: x:
22.37, y: -3.88, z: 0.00
Vertices Point: x:
28.37, y: -3.88, z: 0.00
Vertices Point: x:
24.22, y: 1.82, z: 0.00
Vertices Point: x:
17.52, y: -0.36, z: 0.00
Vertices Point: x:
17.52, y: -7.41, z: 0.00
Vertices Point: x:
24.22, y: -9.59, z: 0.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Vertices Point: x:
22.37, y: -3.88, z: 10.00
Chunk ID: 0x 4160 Size of
Chunk: 54
Chunk ID: 0x 4120 Size of
Chunk: 254
Number of Faces 20
Face: Index1: 0,
Index2: 2, Index3 1
Face: Index1: 2,
Index2: 0, Index3 3
Face: Index1: 2,
Index2: 2, Index3 0
Face: Index1: 4,
Index2: 3, Index3 2
//******cut out to
keep short***********************//
Face: Index1: 4,
Index2: 5, Index3 10
Face: Index1: 6,
Index2: 5, Index3 6
Face: Index1: 10,
Index2: 3, Index3 5
Face: Index1: 1,
Index2: 6, Index3 6
Chunk ID: 0x b000 Size of Chunk: 556 |
There's still more... Yup, as you must agree we could now create a simple 3DS
Loader program, we have our vertices and shape data. But theres one more
little piece of information we need to get. Colours and textures. As
we want to know what colour our shape is don't we, and if it has a texture, what
the textures name is and how to wrap it to our shape. Well it not to hard,
with a few more lines of code we can get that easily.
Stay tuned.
|