www.xbdev.net xbdev - software development
Friday December 14, 2018
home | about | contact | Donations

     
 

3D Formats

Where else are you going to keep your 3D data?...

 

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.

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.