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

Coding MD2 Format - ~Quake 2 File Format~
Author bkenwright@xbdev.net

Well to really understand how a file format works, the best thing you can do it open your compiler and extract the data!  See whats actually inside the .md2 file and confirm your assumptions.

So for this little tutorial I'm going to use the corresponding pac3D.md2 and its corresponding texture is pac3D.bmp... note you won't need the texture in this part of the tutorial as all we'll be doing is just extracting some data from the quake 2 file and confirming that its all in its correct places.

Well I thought I'd give you a quick glimps of whats actually inside our md2 file, as when we get to later stages we will actually use the extracted data to render the 3D model to the screen....OOoooo...how exciting ;).

So on with the tale... first we'll set up some code and read in the first 4 bytes which constitutes our magic number...the ID by which we know that we have a md2 file:

#include <windows.h>

#include <stdio.h>

 

void abc(char *str)

{

      FILE *fp = fopen("output.txt", "a+");

      fprintf(fp, "%s\n", str);

      fclose(fp);

}

 

char buff[500];

char tmp[4];

 

int _stdcall WinMain(HINSTANCE hinstance, HINSTANCE n, char *k, int l)

{

      FILE *f = fopen("pac3D.md2", "rb");

      fread(&tmp, 1, 4, f);

      sprintf(buff, "magic num is: %c%c%c%c", tmp[0], tmp[1], tmp[2], tmp[3]);

      abc(buff);

      fclose(f);

 

      return 1;

}

 

And what amazing output do we expect?  Well if you open the text file "output.txt" you will see that it contains the value IDP2 which is our magic number.

So what does the whole of the header file structure look like, well the next thing we'll do is read in the whole md2 header and then output what we find in it to a text file... the following code accomplishes this:

#include <windows.h>

#include <stdio.h>

 

void abc(char *str)

{

      FILE *fp = fopen("output.txt", "a+");

      fprintf(fp, "%s\n", str);

      fclose(fp);

}

 

char buff[500];

 

struct stMd2Header

{

      int magic;              // The magic number used to identify the file.

      int version;            // The file version number (must be 8).

      int skinWidth;          // The width in pixels of our image.

      int skinHeight;         // The height in pixels of our image.

      int frameSize;          // The size in bytes the frames are.

      int numSkins;           // The number of skins associated with the model.

      int numVertices;  // The number of vertices.

      int numTexCoords; // The number of texture coordinates.

      int numTriangles; // The number of faces (polygons).

      int numGlCommands;      // The number of gl commands.

      int numFrames;          // The number of animated frames.

      int offsetSkins;  // The offset in the file for the skin data.

      int offsetTexCoords;// The offset in the file for the texture data.

      int offsetTriangles;// The offset in the file for the face data.

      int offsetFrames; // The offset in the file for the frames data.

      int offsetGlCommands;// The offset in the file for the gl commands data.

      int offsetEnd;          // The end of the file offset.

};

stMd2Header Md2Header;

 

int _stdcall WinMain(HINSTANCE hinstance, HINSTANCE n, char *k, int l)

{

      FILE *f = fopen("pac3D.md2", "rb");

      fread(&Md2Header, 1, sizeof(Md2Header), f);

      fclose(f);

 

      sprintf(buff, "magic num is: 0x%x...eg. 32='I', 50='D' etc", Md2Header.magic);

      abc(buff);

      sprintf(buff, "version is: %u", Md2Header.version);

      abc(buff);

      sprintf(buff, "skinWidth is: %u", Md2Header.skinWidth);

      abc(buff);

      sprintf(buff, "skinHeight is: %u", Md2Header.skinHeight);

      abc(buff);

      sprintf(buff, "frameSize: %u", Md2Header.frameSize);

      abc(buff);

      sprintf(buff, "numSkins: %u", Md2Header.numSkins);

      abc(buff);

      sprintf(buff, "numVertices: %u", Md2Header.numVertices);

      abc(buff);

      sprintf(buff, "numTexCoords: %u", Md2Header.numTexCoords);

      abc(buff);

      sprintf(buff, "numTriangles: %u", Md2Header.numTriangles);

      abc(buff);

      sprintf(buff, "numGlCommands: %u", Md2Header.numGlCommands);

      abc(buff);

      sprintf(buff, "numFrames: %u", Md2Header.numFrames);

      abc(buff);

      sprintf(buff, "offsetSkins: %u bytes", Md2Header.offsetSkins);

      abc(buff);

      sprintf(buff, "offsetTexCoords: %u bytes", Md2Header.offsetTexCoords);

      abc(buff);

      sprintf(buff, "offsetTriangles: %u bytes", Md2Header.offsetTriangles);

      abc(buff);

      sprintf(buff, "offsetFrames: %u bytes", Md2Header.offsetFrames);

      abc(buff);

      sprintf(buff, "offsetGlCommands: %u bytes", Md2Header.offsetGlCommands);

      abc(buff);

      sprintf(buff, "offsetEnd: %u bytes", Md2Header.offsetEnd);

      abc(buff);

      return 1;

}

So all we have done in the above code is read in the header, which is 68 bytes (e.g. 17 * sizeof(int)).  And if you decide to type it all in and run it, this is what you'll get from the text file:

magic num is: 0x32504449...eg. 32='I', 50='D' etc
version is: 8
skinWidth is: 256
skinHeight is: 256
frameSize: 936
numSkins: 0
numVertices: 224
numTexCoords: 374
numTriangles: 416
numGlCommands: 2089
numFrames: 198
offsetSkins: 68 bytes
offsetTexCoords: 68 bytes
offsetTriangles: 1564 bytes
offsetFrames: 6556 bytes
offsetGlCommands: 191884 bytes
offsetEnd: 200240 bytes

Well it tells us a lot, we now know that our md2 model in this case has 198 frames!  And that that the model has 416 Triangles (e.g. faces).  You could use the above piece of code to dump information from md2 files etc, extract the file data etc.

First thing's first we need to find out  how much space is this model going to cost us in memory, well the following values tells us this:

numSkins: 0 - note in this case its zero, but it could be set to 3 or 6 or any other value, depending on the number of textures.
numTexCoords: 374
numTriangles: 416
numFrames: 198

We should be able to allocate memory based upon these values.

numSkins * char[64] = 0 * char[64] = 0 bytes

numTexCoords * ( 2 * sizeof(short) ) = 374 * ( 2 * 2 ) = 1496 bytes    (note: short tu, short tv).

numTriangles * ( 3 * sizeof(float)   +  3 * sizeof(float) )  = 416 * ( 3*4 +  3*4 ) = 9984 bytes.   (note: float vertex[3], float normal[3] ).

numFrames * ( char[16]  +  3*sizeof(float)  + 3*sizeof(float) ) = 198 * ( 16 + 3*4 + 3*4 ) = 16,640 bytes.   (note: char[16], float vertex[3], float normal[3] ).

 

Well enough of this analysis, I thought it might help to understand how the data is stored etc, and now we will actually read in the data... only the first frame and store it in some dynamically allocated memory, then before exiting we will delete it.  Be warned it may look like a lot of code, but its only simple coding...simple seeking to file location and reading in chunks of data etc... if you go over it once or twice you should get to grips with it.  Once you understand this piece of code you should be able to read in the data from a md2 file and write it out again if you wanted :)

#include <windows.h>

#include <stdio.h>

 

void abc(char *str)

{

      FILE *fp = fopen("output.txt", "a+");

      fprintf(fp, "%s\n", str);

      fclose(fp);

}

 

char buff[500];

 

struct stMd2Header

{

      int magic;              // The magic number used to identify the file.

      int version;            // The file version number (must be 8).

      int skinWidth;          // The width in pixels of our image.

      int skinHeight;         // The height in pixels of our image.

      int frameSize;          // The size in bytes the frames are.

      int numSkins;           // The number of skins associated with the model.

      int numVertices;  // The number of vertices.

      int numTexCoords; // The number of texture coordinates.

      int numTriangles; // The number of faces (polygons).

      int numGlCommands;      // The number of gl commands.

      int numFrames;          // The number of animated frames.

      int offsetSkins;  // The offset in the file for the skin data.

      int offsetTexCoords;// The offset in the file for the texture data.

      int offsetTriangles;// The offset in the file for the face data.

      int offsetFrames; // The offset in the file for the frames data.

      int offsetGlCommands;// The offset in the file for the gl commands data.

      int offsetEnd;          // The end of the file offset.

};

stMd2Header Md2Header;

 

// Some structures to hold or read in data in.

struct stSkins

{

      char skinName[64];

};

 

struct stTexCoords

{

      short u, v;

};

 

struct stTriangles

{

      float vertex[3];

      float normal[3];

};

 

struct stVerts

{

      byte vertex[3]; // an index reference into the location of our vertexs

      byte lightNormalIndex; // in index into which tex coords to use.

};

struct stFrames

{

      float scale[3];

      float translate[3];

      char strFrameName[16];

      stVerts *pVerts;

};

 

 

int _stdcall WinMain(HINSTANCE hinstance, HINSTANCE n, char *k, int l)

{

      FILE *f = fopen("pac3D.md2", "rb");

      fread(&Md2Header, 1, sizeof(Md2Header), f);

 

      // Allocate memory for our data so we can read it in.

      stSkins* pSkins         = new stSkins[ Md2Header.numSkins ];

      stTexCoords* pTexCoords = new stTexCoords[ Md2Header.numTexCoords ];

      stTriangles* pTriangles = new stTriangles[ Md2Header.numTriangles ];

      stFrames* pFrames = new stFrames[ Md2Header.numFrames ];

 

      // -1- Seek to the start of our skins name data and read it in.

      fseek(f, Md2Header.offsetSkins, SEEK_SET);

      fread(pSkins, sizeof(stSkins), Md2Header.numSkins, f);

 

      // -2- Seek to the start of our Texture Coord data and read it in.

      fseek(f, Md2Header.offsetTexCoords, SEEK_SET);

      fread(pTexCoords, sizeof(stTexCoords), Md2Header.numTexCoords, f);

 

      // -3- Seek to the start of the Triangle(e.g. Faces) data and read that in.

      fseek(f, Md2Header.offsetTriangles, SEEK_SET);

      fread(pTriangles, sizeof(stTriangles), Md2Header.numTriangles, f);

 

      // -4- Finally lets read in "one" of the frames, the first one.!

      fseek(f, Md2Header.offsetFrames, SEEK_SET);

      pFrames[0].pVerts = new stVerts[ Md2Header.numVertices ];

      fread(pFrames, 1, Md2Header.frameSize, f);

     

      // CONVERSION!  A few things before we can use our read in values,

      // for some reason the Z and Y need to be swapped, as Z is facing up

      // and Y is facing into the screen.

      // Also our texture coordinates values are between 0 and 256, we just

      // divide them all by 256 which makes them between 0 and 1.

 

      // Swap Z<->Y

      for(int i=0; i< Md2Header.numVertices; i++)

      {

            stVerts tempVert;

            tempVert.vertex[1] = pFrames[0].pVerts[i].vertex[1]; // y

            tempVert.vertex[2] = pFrames[0].pVerts[i].vertex[2]; // z

 

            pFrames[0].pVerts[i].vertex[1] = tempVert.vertex[2]; // z->y

            pFrames[0].pVerts[i].vertex[2] = tempVert.vertex[1]; // y->z

      }

 

      // Scale Textures.

      for (int j=0; j< Md2Header.numTexCoords; j++)

      {

            pTexCoords[j].u = pTexCoords[j].u / float(Md2Header.skinWidth);

            pTexCoords[j].v = pTexCoords[j].v / float(Md2Header.skinHeight);

      }

 

      // Now --Here-- is where we have all our data...if we wanted to could

      // convert of draw it here or something... but since we only wanted

      // to see how its extracted, we just clean up after ourselfs and exit.

 

      // Tidy up before exiting.

      delete[] pFrames[0].pVerts;

 

      delete[] pSkins;

      delete[] pTexCoords;

      delete[] pTriangles;

      delete[] pFrames;

      fclose(f);

 

      return 1;

}

 

Well at first it may look like a lot of code above, but in fact its really simple once yo7u've gone over it once or twice, all it has done is read in the main parts of the file... so that you have in essence, extracted the 3D data which you can use to render you 3D model... Only a single frame has been extracted, but we'll get to animation later.

 

 

 

 

 
 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.