3DS Part 1- "the bits inside"
- The 3DS format from start to finish!
This tutorial is going to be broken down into two sections the first will explain
and show the workings of the .3ds file format as used in 3D Studio Max etc,
and is a valuable piece of knowledge. Why? Well nearly all 3d packages will allow the
import/export of it so. The second part will put it to good use and show it in
action, as we develop a working class that we can re-use over and over again.
So before I start showing you code, and talking about chunks I better give
a quick explanation of how it all works inside. First thing first, if your going
to work with the .3ds format, you better get used to chunks… yup you heard right,
chunks. As for every piece of information in the .3ds file there is a chunk
which is 6 bytes big, which tells us the size of the chunk and what data is in
it.
Our chunk:
2 bytes = Chunk ID. (because its 2 bytes, a short int can be used to
represent it).
4 bytes = Chunk length in bytes. (again an unsigned integer is 4 bytes, perfect
to represent the size of our chunk).
Put the computer down…don’t smash the screen because I’ve lost you at such an
early stage…lol. Lets do this with code so that you can understand what I mean.
Okay okay, this is as simple and easy to follow as I can make it. All it does
it make sure that we are dealing with a .3ds file. How does it work? Well if
you don’t know what fread(..) function is your in trouble. It’s a standard c
function which is included in the <stdio.h> library.
#include
<stdio.h>
// I'm going to read in the data from the file and output it
// to a text file called fileinfo.txt, which will be in the
// same directory as the .3ds file and .exe file.
void
output(char *string)
{
FILE* fp
= fopen("fileinfo.txt", "a+");
fprintf(fp,"%s", string);
fclose(fp);
}
// Just pick any .3ds file at random, and specify its name.
char
filename[] = "box.3ds";
// Program entry point.
void
main()
{
// Part-1- Open the file
FILE* fp
= fopen(filename, "rb");
// Part-2- Create some variables to read our data
in.
char ID[2];
unsigned int
chunksize;
// Part-3- Read in the 6 bytes of data from the
start of the file.
//
Buffer to put our data in
// | Size in byte of data to read
// | | Number of times to read (e.g.
2 sets of one byte.
// | | | Pointer to the open
file.
// | | | |
fread(ID, 2, 1, fp);
fread(&chunksize,
4, 1, fp);
// Part-4- Close our file and display what we have
read in.
fclose(fp);
// Large text buffer.
char buff[200];
sprintf(buff, "First byte, ID[0]: %x\n", ID[0]);
output(buff);
sprintf(buff, "Second byte, ID[1]: %x\n", ID[1]);
output(buff);
sprintf(buff, "Next 4 bytes, chunksize: %u\n", chunksize);
output(buff);
}
So whats happening in this section of code, and what does
it tell us? Well if you run it you’ll get an output file called “fileinfo.txt”
which contains:
First byte, ID[0]: 4d
Second byte, ID[1]: 4d
Next 4 bytes, chunksize: 595
About the code, we open the .3ds file, (in this case
box.3ds) and we read in the very first byte, and it is 4D in hex, the byte right
after it is read in and has the value of 4D. This is the chunk ID (4D4D). We
read the next 4 bytes into an unsigned integer, and what does it have?? Well
it’s the size of the chunk… and the very first chunk (ID of 4D4D) contains all
the other chunks and so the chunksize is the size of the file.
So if you open the file properties and check its file size
you will find out that the size of the file is 595 bytes
J.
Now where has this taken us? You should know what a chunk
is, its best described as:
struct stChunk
{
unsigned
short ID; // 2
bytes
unsigned
int size; // 4
bytes
};
So now our code would become:
#include
<stdio.h>
void
output(char *string)
{
FILE* fp
= fopen("fileinfo.txt", "a+");
fprintf(fp,"%s", string);
fclose(fp);
}
// Just pick any .3ds file at random, and specify its name.
char
filename[] = "box.3ds";
void
main()
{
// Part-1- Open the file
FILE* fp
= fopen(filename, "rb");
// Part-2- Create some variables to read our data
in.
struct stChunk
{
unsigned short
ID; // 2 bytes
unsigned int
size; // 4 bytes
};
stChunk
myChunk;
fread(&myChunk.ID,
2, 1, fp); // Read in 2 bytes.
fread(&myChunk.size,
4, 1, fp); // Read in 4 bytes.
fclose(fp);
// Large text buffer.
char buff[200];
// Write our data to the text file.
sprintf(buff, "Chunk ID 0x%x\n", myChunk.ID);
output(buff);
sprintf(buff, "Size of Chunk: %u\n", myChunk.size);
output(buff);
}
Well its all becoming nice and tidy now... we just read in
the chunks and then we know what data is in the chunk from its ID, and how big
it is from the size of the chunk. The next big thing is to know what all
the chunks are and what they do. Now there's a big big load of ID's which
does everything from camera position to the simple x,y,z position of the vertex.
I'll be putting together a whole document describing them later on when I get
time, but I'm sure with a bit of help from google and yahoo you can find some if
you really need to :)
So the plot thickens...
//>----- Entry point (Primary Chunk at the start of the file ----------------
#define
PRIMARY 0x4D4D
//>----- Main Chunks --------------------------------------------------------
#define
EDIT3DS 0x3D3D // Start of our
actual objects
#define
KEYF3DS 0xB000 // Start of the
keyframe information
//>----- General Chunks -----------------------------------------------------
#define
VERSION 0x0002
#define
MESH_VERSION 0x3D3E
#define
KFVERSION 0x0005
#define
COLOR_F 0x0010
#define
COLOR_24 0x0011
#define
LIN_COLOR_24 0x0012
#define
LIN_COLOR_F 0x0013
#define
INT_PERCENTAGE 0x0030
#define
FLOAT_PERC 0x0031
#define
MASTER_SCALE 0x0100
#define
IMAGE_FILE 0x1100
#define
AMBIENT_LIGHT 0X2100
//>----- Object Chunks -----------------------------------------------------
#define
NAMED_OBJECT 0x4000
#define
OBJ_MESH 0x4100
#define
MESH_VERTICES 0x4110
#define
VERTEX_FLAGS 0x4111
#define
MESH_FACES 0x4120
#define
MESH_MATER 0x4130
#define
MESH_TEX_VERT 0x4140
#define
MESH_XFMATRIX 0x4160
#define
MESH_COLOR_IND 0x4165
#define
MESH_TEX_INFO 0x4170
#define
HEIRARCHY 0x4F00
//>----- Material Chunks ---------------------------------------------------
#define
MATERIAL 0xAFFF
#define
MAT_NAME 0xA000
#define
MAT_AMBIENT 0xA010
#define
MAT_DIFFUSE 0xA020
#define
MAT_SPECULAR 0xA030
#define
MAT_SHININESS 0xA040
#define
MAT_FALLOFF 0xA052
#define
MAT_EMISSIVE 0xA080
#define
MAT_SHADER 0xA100
#define
MAT_TEXMAP 0xA200
#define
MAT_TEXFLNM 0xA300
#define
OBJ_LIGHT 0x4600
#define
OBJ_CAMERA 0x4700
//>----- KeyFrames Chunks --------------------------------------------------
#define
ANIM_HEADER 0xB00A
#define
ANIM_OBJ 0xB002
#define
ANIM_NAME 0xB010
#define
ANIM_POS 0xB020
#define
ANIM_ROT 0xB021
#define
ANIM_SCALE 0xB022
Now before you give up and think that its a lot to swallow
don't worry about it...... These are all the chunk ID's you'll ever need
in the real world... what they all do I'll show you in time. Now me, I've
just put them all in a separate file called
chunkdetails.cpp as you usually never need to actually remember all there
ID's... its better to go, " if( ID = = PRIMARY) " than if you go, if( ID = =
4D4D).
Believe it or not you've come a long way in such a short
time. If you've grasped this chunk stuff, your home and dry practically...
just stay with me a few more lines and we'll get you out ... you better go and
get some coffee as well, make it a 400g jar.
"News News".... skipping chunks isn't illegal.
Okay this is very important, especially if your new to the
.3ds format. If you don't know a chunk ID or you just don't want the data
from that chunk, just read past it... you know how big it is, so you can just
read to the end of the chunk and read in the next chunk ID and size. Cool
? :)
So where next?... well people new to the format could
get lost very very easily at this point, so lets just get the vertex data...you
know...the points that make up our shape... x,y,z for each triangle... still
with me? If at this point your wondering what a vertex is... I'm wondering
how you got this for...lol
Okay the code that follows is the foundation of creating
your own .3ds reader... from this you should be able to go a long way, it allows
you to read in the chunk and read past it. Once I show you how the data is
arranged in the chunks, e.g. how to get the vertex's the colours etc...your
fixed for life :)
// Our .3DS file that we'll use for testing.
#define
FILENAME "box.3ds"
/***************************************************************************/
// These few lines are compiler directives that include the windows librarys
// during linking. You could instead inside visual studio goto Project->
// settings" menu and under the "Link" tab add the necessary librarys instead.
#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
output(char *str)
{
FILE
*fp;
fp =
fopen("filedata.txt", "a+");
fprintf(fp, "%s", str);
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);
}
/***************************************************************************/
/* */
/* Read in .3ds file. */
/* */
/***************************************************************************/
void
read3ds()
{
FILE*
pFile;
pFile
= fopen(FILENAME, "rb");
// Large buffer for our text.
char buf[1000] = {0};
// I've created a structure called stChunk, which
should be self explanatory
// and holds the ChunkID, its size (in bytes) and a
bytes read counter...so
// as we read into it we know how many bytes are
left.
stChunk Chunk = {0};
// This is where it starts to become organised, now
instead of using fread etc
// and specifying to read in the chunk information,
we just create a funciton
// called ReadChunk and it will read the information
into our chunk.
// This is our very first chunk read... and its
details should be:
// First Chunk ID: 0x 4d 4d
// Size of Chunk: 595
ReadChunk(pFile, &Chunk);
sprintf(buf, "Chunk ID: 0x %x \n", Chunk.ID);
output(buf);
// Now once we are sure we have a .3ds file (e.g.
the very very first chunk
// we read in has an ID of 4D4D then we can read in
all the sub chunks.
while(Chunk.bytesRead < Chunk.length)
{
stChunk tempChunk = {0};
ReadChunk(pFile, &tempChunk);
sprintf(buf, "\tChunk ID: 0x %04x \n", tempChunk.ID);
output(buf);
// This function is simple it will just read past
all the contents of a
// chunk.
SkipChunk(pFile, &tempChunk);
Chunk.bytesRead += tempChunk.length;
}
fclose(pFile);
}
/***************************************************************************/
/* */
/* The program entry point, this is where we start and end. */
/* */
/***************************************************************************/
void
main()
{
// I'm going to put the 3ds information in seperate
files, so that as we
// go along we can start to create a independent set
of function (and
// eventually a class) which will work with windows,
and other platforms.
read3ds();
}
So what does the output file look like? filedata.txt
output:
Chunk ID: 0x 4d4d
Chunk ID: 0x 0002
Chunk ID: 0x 3d3d
Chunk ID: 0x b000
It may not look like much, but if you look at the meaning
for each 3DS chunk you'll soon start to get it.
Chunk ID: 0x4d4d...well hope by now you know this is the
entry point for our .3ds file! Its the very first first chunk, and its
size is the size of the file in bytes. Next is 0x002 which if you look at the
file chunkdetails.cpp you'll find that it is called "VERSION",
now I hope this speaks for itself, but the data in this chunk is an integer
which represents the version number e.g. 3. Next is 0x3d3d which if
you look at the chunkdetails.cpp file again you'll see that is called "EDIT3DS",
now all your 3D shape information goes in here... colour, number of vertices
etc. And finalllllyyyy we have a door number 3, 0xb000 which is the
animation details ("KEYF3DS"),
as you can animate an object in 3d studio max and save its key frame animation
data in here.
Remember this is a beginners guide, so the only information
I'm going to get for you is the vertex data! With this vertex data you can
draw the shape, later on I'll show you how to get the other information such as
colour, key frame details etc.
Now the following piece of code is the reason why I add
"download code" options to my web site. Its a big piece of code, but if
you've been following me up to now you'll find it pretty simple and easy to
follow. This is the first time I've actually included
"chunkdetails.h"
in the file.
// Our .3DS file that we'll use for testing.
#define
FILENAME "box.3ds"
/***************************************************************************/
// These few lines are compiler directives that include the windows librarys
// during linking. You could instead inside visual studio goto Project->
// settings" menu and under the "Link" tab add the necessary librarys instead.
#include
<stdio.h>
// I've put the chunk definitions in a separate 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
output(char *str)
{
FILE
*fp;
fp =
fopen("filedata.txt", "a+");
fprintf(fp, "%s", str);
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);
}
/***************************************************************************/
/* */
/* This is what I like to call a chunk feedback function...lol... */
/* I thought it was tidier to just pass the chunk to the function every */
/* time I read in a new chunk and have it display the chunk details, e.g. */
/* its ID and its size (in bytes). */
/* */
/***************************************************************************/
void
DisplayChunkInfo(stChunk* pChunk)
{
char buf[1000] = {0};
sprintf(buf, "Chunk ID: 0x %04x Size of Chunk: %u\n", pChunk->ID, pChunk->length);
output(buf);
}
/***************************************************************************/
/* */
/* Now we are going to display all the branches of the "EDIT3DS" chunk. */
/* E.g. chunks within chunks :) */
/* */
/***************************************************************************/
void
ReadEDIT3DS(FILE *fp, stChunk* Chunk)
{
// Just as for the main chunk, I've passed the chunk
detais to the function,
// so I know how big it is, and so I can just keep
reading all the chunks in
// it until I reach the end, then return.
while(Chunk->bytesRead < Chunk->length)
{
stChunk tempChunk = {0};
ReadChunk(fp, &tempChunk);
DisplayChunkInfo(&tempChunk);
// I'll add a case statement later, but first things
first lets take
// a looksy inside the "EDIT3DS" (0x3D3D) chunk, and
see what sub chunks
// are really in there.
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);
// Loop and loop around, until we reach the end of
the big chunk, the first
// chunk the chunk with the ID of 4d4d :)
while(Chunk.bytesRead < Chunk.length)
{
stChunk tempChunk = {0};
ReadChunk(pFile, &tempChunk);
DisplayChunkInfo(&tempChunk);
unsigned int
bytesRead = 0;
switch( tempChunk.ID)
{
// Now where going to go to another function to read
in all our 3D
// shape information....why hang around in here,
when it will only
// make single complex function... best to break it
up into tidy sections.
case EDIT3DS:
ReadEDIT3DS(pFile, &tempChunk);
bytesRead = tempChunk.length;
break;
// Where not going to bother with the animation
chunks at this early
// stage, so we just read past them.
case KEYF3DS:
SkipChunk(pFile, &tempChunk);
bytesRead = tempChunk.length;
break;
// Other junks, like junks added to newer versions,
and the version
// chunk I'm just going to skip, as its a tutorial
and it just makes
// the code hard to follow.
default:
SkipChunk(pFile, &tempChunk);
bytesRead = tempChunk.length;
}
Chunk.bytesRead += bytesRead;
}
fclose(pFile);
}
/***************************************************************************/
/* */
/* The program entry point, this is where we start and end. */
/* */
/***************************************************************************/
void
main()
{
read3ds();
}
So the output file is starting to grow now, and it contains
a lot of juicy info that you should be able to look at and see what's going on
inside all this.. filedata.txt contains:
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
Let me explain what this information tells us, the more
times I tell you the better... I don't want you forgetting or getting lost along
the way.
Chunk ID: 0x 4d4d Size of Chunk: 595 ->
"PRIMARY" chunk entry point...and the size of the file...595 bytes.
Chunk ID: 0x 0002 Size of Chunk: 10 -> "VERSION"
chunk (sub chunk of Primary Chunk), and is 10 bytes, which includes the 6 bytes
for the header, the other 4 bytes is an unsigned integer which you could
read in and it would represent the version number.
Chunk ID: 0x 3d3d Size of Chunk: 360 -> "EDIT3DS" chunk, which
I like to think of as the 3d data chunk, as its our 3d objets for our shape
which is in here. And the same as before, its a baby of the primary chunk.
Chunk ID: 0x 3d3e Size of Chunk: 10 -> "MESH_VERSION"
chunk, simply the version used to store the vertex data....again 10 bytes, 6 for
the chunk, and 4 for the unsigned integer which should be a number.... we can
ignore this. Later on we'll add some error checking to make sure its a 3,
for version 3 for the .3ds format.
Chunk ID: 0x 0100 Size of Chunk: 10 -> "MASTER_SCALE"
chunk - this is a scale value, telling us? Yup how much to scale our
object, 2 for twice as big 4 for four times as big.... I think you get the
picture.
Chunk ID: 0x 4000 Size of Chunk: 334 -> "NAMED_OBJECT" chunk -
inside here we have our object, in this case our cube :) We could have
numerous NAMED_OBJECT chunks, one for a sphere, another cube etc...which we
could read in.... but we only going to read in one object.
Chunk ID: 0x b000 Size of Chunk: 219 -> "KEYF3DS" chunk - yup the
animation stuff... as I said... we don't want to open pandoras box of code yet.
At his point in time, I hope your coming to terms with
bytes and what I've been doing! Remember, char is a byte, a integer is 4
bytes, short integer is 2 bytes, and a float is 4 bytes.
|