MD3 Format - A Look Inside...
Author bkenwright@xbdev.net
When you first start with a new file format, I think its best
to just rip it to pieces....to disect it byte by byte so you know exactly what
your dealing with. So I got a simple quake3 file, its called 'sarge' and
is in the media folder with this demo.
Usually you get the quake3 md3 file zipped up, so if you
download one from planetquake or some other website, you'll can just rename it
to a .zip and unzip it. Then inside it you'll find various files.
Usually, as in our sarge demo, you'll get 3 md3 files which contain the actual
meshes and animation data, and 3 .skin files which define the graphics and of
course animation.cfg, which define which animations are from which frame index
to what.
The .md3 files are binary, while the .skin and .cfg files can be
opened in notepad and are text based.
<Sarge Files>
---
animation.cfg
head.md3
head_default.skin
lower.md3
lower_default.skin
upper.md3
upper_default.skin
band.tga
cigar.tga
---
For this demo I've just used the upper.md3 file, as once we can
load in one .md3 file, we can do a class/structure for each .md3 and load all 3
in, then we link them together to form that full character.
Download Code |
/***************************************************************************/
/* */
/* File: main.cpp */
/* Author: bkenwright@xbdev.net */
/* URL: www.xbdev.net */
/* Date: 19-03-2006 (easter) */
/* */
/***************************************************************************/
/*
Understanding the Quake3 MD3 File Format
*/
//---------------------------------------------------------------------------
#define SZ_MD3_FILE "media\\model\\sarge\\upper.md3"
#define MAX_FILENAME_LENGTH 256
//---------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h> //sprintf(...)
//---------------------------------------------------------------------------
//Saving debug information to a log file
void abc(char *str)
{
FILE *fp = fopen("output.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}
//---------------------------------------------------------------------------
struct stMD3Header
{
char ID[4]; // "IDP3"
int Version; // 15
char Filename[68];
int numBoneFrames;
int numTags;
int numMeshes;
int numMaxSkins;
int headerLength;
int TagStart;
int TagEnd;
int FileSize;
};
struct stBoneFrame
{
float mins[3];
float maxs[3];
float Position[3];
float Scale;
float Creator[16];
};
struct stAnim
{
int FirstFrame;
int numFrames;
int LoopingFrames;
int FPS;
};
struct stTag
{
char Name[64];
float Position[3];
float Rotation[3][3];
};
struct stTriangle
{
int Vertex[3];
};
struct stTexCoord
{
float Coord[2];
};
struct stVertex // = Record
{
short int Vertex[3];
unsigned char Normal[2];
};
struct stMeshHeader
{
char ID[4];
char Name[68];
int numMeshFrames;
int numSkins;
int numVertexes;
int numTriangles;
int triStart;
int headerSize;
int TexVectorStart;
int VertexStart;
int MeshSize;
};
struct stMesh
{
stMeshHeader MeshHeader;
char Skins[68];
stTriangle Triangle;
stTexCoord TexCoord;
stVertex Vertex;
unsigned int Texture;
bool SetTexture;
};
//---------------------------------------------------------------------------
long filesize(FILE *stream)
{
long curpos, length;
curpos = ftell(stream);
fseek(stream, 0L, SEEK_END);
length = ftell(stream);
fseek(stream, curpos, SEEK_SET);
return length;
}
//---------------------------------------------------------------------------
class CMD3
{
public:
char m_md3FileName[MAX_FILENAME_LENGTH];
stMD3Header m_md3Header;
stBoneFrame m_boneFrame;
stTag m_tags;
stMesh m_meshes[100];
//-----------------------------------------------------------------------
//
// Loads model from a .md3 file
//
//-----------------------------------------------------------------------
bool LoadModel(char* filename)
{
char buf[256];
FILE* fp = fopen(filename, "rb");
if (fp==NULL)
{
abc("unable to open file");
return false;
}
int md3filesize = filesize(fp);
fseek(fp, 0L, SEEK_SET);
if (strlen(filename)>255)
{
sprintf(buf, "filename is longer than %d", MAX_FILENAME_LENGTH);
abc(buf);
return false;
}
// copy name
strcpy(m_md3FileName, filename);
sprintf(buf, "MD3 FileName: %s", m_md3FileName);
abc(buf);
sprintf(buf, "FileSize: %d", md3filesize);
abc(buf);
abc("\n~~MD3 Header~~\n");
// read header
fread(&m_md3Header, 1, sizeof(stMD3Header), fp);
// log debug information to file
sprintf(buf, "ID %c%c%c%c", m_md3Header.ID[0], m_md3Header.ID[1], m_md3Header.ID[2], m_md3Header.ID[3]);
abc(buf);
sprintf(buf, "Version: %d", m_md3Header.Version);
abc(buf);
sprintf(buf, "FileName: %s", m_md3Header.Filename);
abc(buf);
sprintf(buf, "numBoneFrames: %d", m_md3Header.numBoneFrames);
abc(buf);
sprintf(buf, "numTags: %d", m_md3Header.numTags);
abc(buf);
sprintf(buf, "numMeshes: %d", m_md3Header.numMeshes);
abc(buf);
sprintf(buf, "numMaxSkins: %d", m_md3Header.numMaxSkins);
abc(buf);
sprintf(buf, "headerLength: %d", m_md3Header.headerLength);
abc(buf);
sprintf(buf, "TagStart: %d", m_md3Header.TagStart);
abc(buf);
sprintf(buf, "TagEnd: %d", m_md3Header.TagEnd);
abc(buf);
sprintf(buf, "FileSize: %d", m_md3Header.FileSize);
abc(buf);
if (strcmp("IDP3", m_md3Header.ID)==NULL)
{
sprintf(buf, "Incorrect File Format 'Incorrect ID' ie. ('IDP3')");
abc(buf);
}
/*
// read boneframes
fread(&m_boneFrame[0], 1, m_md3Header.numBoneFrames*sizeof(stBoneFrame), fp);
// read tags
fread(&m_tags[0], m_md3Header.numBoneFrames*m_md3Header.numTags*sizeof(stTag));
int MeshOffset = ftell(fp);
//For I :=0 to Header.numMeshes-1 do
// begin
for (int i=0; i<MD3Header.numMeshes; i++)
{
fseek(fp, MeshOffset, SEEK_SET);
// Load the Skins
fread(Meshes[i].Skins[0], 68 * Meshes[i].MeshHeader.numSkins, fp);
// Triangles
fseek(fp, MeshOffset + Meshes[i].MeshHeader.triStart, SEEK_SET);
fread(Meshes[i].Triangle[0], sizeof(stTriangle)*Meshes[i].MeshHeader.numTriangles, fp);
// Texture Coordiantes
fseek(fp, MeshOffset + Meshes[i].MeshHeader.TexVectorStart);
fread(Meshes[i].TexCoord[0], sizeof(stTexCoord)*Meshes[i].MeshHeader.numVertexes, fp);
// Vertices
fseek(fp, MeshOffset + Meshes[i].MeshHeader.VertexStart);
fread(Meshes[i].Vertex[0], sizeof(stVertex)*Meshes[i].MeshHeader.numVertexes * Meshes[i].MeshHeader.numMeshFrames);
MeshOffset = MeshOffset + Meshes[i].MeshHeader.MeshSize;
}
*/
fclose(fp);
return true;
}// End LoadModel(..)
};
//---------------------------------------------------------------------------
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static CMD3 md3;
md3.LoadModel(SZ_MD3_FILE);
// If something was not done, let it go
return 0;
}
//---------------------------------------------------------------------------
|
Below shows what your likely to see if you run the above code,
we use the standard c library functions to read in various information from the
header. This header information tells us that its a quake3 file, how many
bytes long it is, and the offsets to the various other information that we need,
such as vertices, bones etc.
Output.txt |
MD3 FileName: media\model\sarge\upper.md3
FileSize: 468636
~~MD3 Header~~
ID IDP3
Version: 15
FileName: models/players/sarge/sarge.md3
numBoneFrames: 153
numTags: 3
numMeshes: 2
numMaxSkins: 0
headerLength: 108
TagStart: 8676
TagEnd: 60084
FileSize: 468636 |
|