xbdev - software development
Saturday October 24, 2020
home | about | contact | Donations


3D File Formats

The bits and bytes...


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>





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"



#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);


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
    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);
            return false;
        // copy name
        strcpy(m_md3FileName, filename);
        sprintf(buf, "MD3 FileName: %s", m_md3FileName);

        sprintf(buf, "FileSize: %d", md3filesize);

        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]);

        sprintf(buf, "Version: %d", m_md3Header.Version);

        sprintf(buf, "FileName: %s", m_md3Header.Filename);

        sprintf(buf, "numBoneFrames: %d", m_md3Header.numBoneFrames);

        sprintf(buf, "numTags: %d", m_md3Header.numTags);

        sprintf(buf, "numMeshes: %d", m_md3Header.numMeshes);

        sprintf(buf, "numMaxSkins: %d", m_md3Header.numMaxSkins);

        sprintf(buf, "headerLength: %d", m_md3Header.headerLength);

        sprintf(buf, "TagStart: %d", m_md3Header.TagStart);

        sprintf(buf, "TagEnd: %d", m_md3Header.TagEnd);

        sprintf(buf, "FileSize: %d", m_md3Header.FileSize);

        if (strcmp("IDP3", m_md3Header.ID)==NULL)
            sprintf(buf, "Incorrect File Format 'Incorrect ID' ie. ('IDP3')");

        // 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;


        return true;
    }// End LoadModel(..)



INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
    static CMD3 md3;


    // 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.

MD3 FileName: media\model\sarge\upper.md3
FileSize: 468636

~~MD3 Header~~

Version: 15
FileName: models/players/sarge/sarge.md3
numBoneFrames: 153
numTags: 3
numMeshes: 2
numMaxSkins: 0
headerLength: 108
TagStart: 8676
TagEnd: 60084
FileSize: 468636








 Visitor: 9534626
{ }
Copyright (c) 2002-2020 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.