www.xbdev.net
xbdev - software development
Friday May 22, 2026
Home | Contact | Support | 3D File Formats The bits and bytes... | 3D File Formats The bits and bytes...
>>
     
 

3D File Formats

The bits and bytes...

 

Quake 3 BSP [Reading in File Information]
Author bkenwright@xbdev.net



Quake 3 BSP


Let's get our hands dirty and jump into the code. Implementing an Q3 BSP reader/parser isn't as hard as you'd think.

As you'll see by looking at the code below the Quake 3 file format is pretty well layed out.

It's mostly just breaking it down and analysing the data.

#include <stdio.h> // fopen(..), fclose(..)
 
#define  SZ_FILE_BSP "test_level.bsp"  // Quake 3 BSP Level File
  
//------------------------- Various Structure Definitions ------------------------//
struct stLump
{
      int fileofs;
      int filelen;
};
 
struct stHeader
{
      char id[4];
      int ver;
      stLump lumps[17];
};
 
//-----------------------Write Debug Feedback Information-------------------------//
void abc(char *str)
{
      FILE *fp = fopen("dbg.txt", "a+");
      fprintf(fp, "%s", str);
      fclose(fp);
}
 
//-------------------------- Program Entry Point ---------------------------------//
void main()
{
      abc("<->Opening File\n");
 
      // Open BSP File
      FILE * fp = fopen(SZ_FILE_BSP, "rb");
     
      // Temporary Buffer
      stHeader Data;
      fread(&Data,                // Buffer
              sizeof(stHeader),     // Size
              1,                    // Number of times
              fp);                  // File Pointer
 
      // Temp string buffer
      char buf[100];
      sprintf(buf, "<?>stHeader:id = %c%c%c%c\n",
                         Data.id[0], // 'I'
                         Data.id[1], // 'B'
                         Data.id[2], // 'S'
                         Data.id[3]);// 'P'
      abc(buf);
 
      sprintf(buf, "<?>stHeader:ver = 0x%X\n",
                        Data.ver);   // 0x2E for Quake3
      abc(buf);
     
      abc("<->Closing File\n");
      // Close BSP File
      fclose(fp);
}//End main()
Output File:
<->Opening File
<?>stHeader:id = IBSP
<?>stHeader:ver = 0x2E
<->Closing File
 
 
Taking it a stage further we can get now dump the location in our file of the various lump data!
Download Source Code
#include <stdio.h>                                  // fopen(..), fclose(..)
 
#define  SZ_FILE_BSP                "test_level.bsp"  // Quake 3 BSP Level File
 
//----------------------- Various BSP Lump Data Types-----------------------------//
enum BSP_TYPES
{
      Entities=0,
      Textures,
      Planes,
      Nodes,
      Leaves,
      LeafFaces,
      LeafBrushes,
      Models,
      Brushes,
      BrushSides,
      Vertices,
      MeshIndices,
      Effect,
      Faces,
      Lightmaps,
      LightVols,
      VisData
};
 
char szBSPTYPES [17][20] = {
      "Entities",
      "Textures",
      "Planes",
      "Nodes",
      "Leaves",
      "LeafFaces",
      "LeafBrushes",
      "Models",
      "Brushes",
      "BrushSides",
      "Vertices",
      "MeshIndices",
      "Effect",
      "Faces",
      "Lightmaps",
      "LightVols",
      "VisData" };
 
//------------------------- Various Structure Definitions ------------------------//
struct stLump
{
      int         nFileofs;
      int         nFileLen;
};
 
struct stHeader
{
      char   cMagic[4];
      int      nVersion;
      stLump Lumps[17];
};
 
//-----------------------Write Debug Feedback Information-------------------------//
void abc(char *str)
{
      FILE *fp = fopen("dbg.txt", "a+");
      fprintf(fp, "%s", str);
      fclose(fp);
}
 
 
//-------------------------- Program Entry Point ---------------------------------//
void main()
{
      abc("<->Opening File\n\n");
 
      // Open BSP File
      FILE * fp = fopen(SZ_FILE_BSP, "rb");
     
      // Temporary Buffer
      stHeader Data;
      fread(&Data,                              // Buffer
              sizeof(stHeader),                 // Size
              1,                                // Number of times
              fp);                              // File Pointer
 
      // Temp string buffer
      char buf[100];
      sprintf(buf, "<?>stHeader:cMagic = %c%c%c%c\n",
                         Data.cMagic[0],  // 'I'
                         Data.cMagic[1],  // 'B'
                         Data.cMagic[2],  // 'S'
                         Data.cMagic[3]); // 'P'
      abc(buf);
 
      sprintf(buf, "<?>stHeader:ver = 0x%X\n\n",
                         Data.nVersion);  // 0x2E for Quake3
      abc(buf);
 
      // Get the pointer to our array of lumps
      stLump * pLumps = Data.Lumps;
      // Display all the different types of Lumps
      for(int i=0; i<17; i++)
      {
           
            sprintf(buf, "<?>\tLump:(%d):%s"
                               "FileOffset: 0x%X"
                               "Length: 0x%X\n",
                           i+1,
                               szBSPTYPES[i],
                               pLumps[i].nFileofs,
                               pLumps[i].nFileLen);
            abc(buf);
      }
 
 
      abc("\n<->Closing File\n");
      // Close BSP File
      fclose(fp);
}//End main()


Debug File Output:

<->Opening File
<?>stHeader:cMagic = IBSP
<?>stHeader:ver = 0x2E
<?> Lump:(1):   EntitiesFileOffset: 0x355FCLength: 0x948
<?> Lump:(2):   TexturesFileOffset: 0x90Length: 0x318
<?> Lump:(3):   PlanesFileOffset: 0x3A8Length: 0xD00
<?> Lump:(4):   NodesFileOffset: 0x2308Length: 0xD80
<?> Lump:(5):   LeavesFileOffset: 0x10A8Length: 0x1260
<?> Lump:(6):   LeafFacesFileOffset: 0x49D8Length: 0x4F4
<?> Lump:(7):   LeafBrushesFileOffset: 0x4ECCLength: 0x364
<?> Lump:(8):   ModelsFileOffset: 0x5230Length: 0x28
<?> Lump:(9):   BrushesFileOffset: 0x3088Length: 0x450
<?> Lump:(10): BrushSidesFileOffset: 0x34D8Length: 0x1500
<?> Lump:(11): VerticesFileOffset: 0x5258Length: 0x11D24
<?> Lump:(12): MeshIndicesFileOffset: 0x35F44Length: 0x34E0
<?> Lump:(13): EffectFileOffset: 0x35F44Length: 0x0
<?> Lump:(14): FacesFileOffset: 0x16F7CLength: 0x3AE8
<?> Lump:(15): LightmapsFileOffset: 0x1ABBCLength: 0x18000
<?> Lump:(16): LightVolsFileOffset: 0x32BBCLength: 0x2A40
<?> Lump:(17): VisDataFileOffset: 0x1AA64Length: 0x158
<->Closing File


We have a considerable amount of information, but nothing we can really get our teeth into and use. We'll put together a load of structures so we can actually read in all our data. Probably the most important information is the vertices and mesh indices. So we'll dump them to our file and make sure they look write.


#include <stdio.h>                                    // fopen(..), fclose(..)
 
#include <assert.h>                                   // assert(..)
 
#define  SZ_FILE_BSP          "test_level.bsp"        // Quake 3 BSP Level File
 
//----------------------- Various BSP Lump Data Types-----------------------------//
enum BSP_TYPES
{
      enEntities=0,
      enTextures,
      enPlanes,
      enNodes,
      enLeaves,
      enLeafFaces,
      enLeafBrushes,
      enModels,
      enBrushes,
      enBrushSides,
      enVertices,
      enMeshIndices,
      enEffect,
      enFaces,
      enLightmaps,
      enLightVols,
      enVisData
};
 
char szBSPTYPES [17][20] = {
      "Entities",
      "Textures",
      "Planes",
      "Nodes",
      "Leaves",
      "LeafFaces",
      "LeafBrushes",
      "Models",
      "Brushes",
      "BrushSides",
      "Vertices",
      "MeshIndices",
      "Effect",
      "Faces",
      "Lightmaps",
      "LightVols",
      "VisData" };
 
 
//------------------------- Various Structure Definitions ------------------------//
struct stLump
{
      int         nFileofs;               // Offset to start of lump, relative to beginning of file.
      int         nFileLen;               // Length of lump. Always a multiple of 4.
};
 
struct stHeader
{
      char   cMagic[4];
      int    nVersion;
      stLump Lumps[17];
};
 
//----------------------------- Lump Definitions ---------------------------------//
 
typedef float     Vector2[2];
typedef float     Vector3[3];
typedef float     Vector4[4];
 
typedef float     TexCoord[2];
 
typedef int       nBBox[6]; // Integer bounding box (mins, maxs)
 
typedef float     fBBox[6]; // Float bounding box
 
 
//Lump 0
//Entities
char *entities; // A pointer to text
 
 
//Lump 1
//Shader Texture Info
struct stShaderRef
{
      char  Name[64];         // Texture name
      int         nSurfaceFlags;    // Type of surface (See Surface Flags below)
      int         nContentFlags;    // Leaf content (See Content Flags below)
};
 
 
//Lump 2
//Planes
struct stPlane
{
      Vector3 Normal;               // Normal vector for plane
      float fDist;                  // Distance from plane to origin
};
 
 
//Lump 3
//Nodes
struct stNode
{
      int         nPlane;                 // Space partitioning plane
      int         nChildren[2];     // Back and front child nodes
      nBBox BBoxI;                  // Bounding box of node
};
 
//Lump 4
//Leaves
struct stLeaf
{
      int         nCluster;         // Visibility cluster number
      int         nArea;                  // Volume of the leaf
      nBBox BBoxI;                  // Bounding box of leaf
 
      int         nFirstFace,
                  NumFaces;         // Lookup for the face list (indexes
                                          // are for faces)
 
      int         nFirstBrush,
                  NumBrushes;       // Lookup for the brush list (indexes
                                          // are for brushes)
};
 
//Lump 5
//Leaf Faces
 int *pFaces;                       // a pointer to a series of indexes to
                                          // a face list
 
//Lump 6
//Leaf Brushes
int *pBrushes;                      // a pointer to a series of indexes to
                                          // a brush list
//Lump 7
//Models
struct stModel
{
      fBBox BBoxF;                  // Bounding box of model
 
      int         nFirstFace,       // First face for model
                  NumFaces;         // Number of faces for model
 
      int         nFirstBrush,      // First brush for model
                  NumBrushes;       // Number of brushes for model
};
 
//Lump 8
//Brushes
struct stBrush
{
      int         nFirstSide,       // First brushside for brush
                  NumSides;         // Number of brushsides for brush
 
      int         nIndex;                 // Texture index
};
 
//Lump 9
//Brush Sides
struct stBrushSide
{
      int         PlaneNum;         // Lookup for plane
      int         nIndex;                 // Texture index
};
 
//Lump 10
//Vertices
struct stVertex
{
      Vector3  vPoint;        // Vertex Position
      TexCoord Tex;                 // Texture coordinates
      TexCoord LightTexCoord; // Light Map texture coordinates
      Vector3  vNormal;       // Normal vector (used for lighting ?)
 
      unsigned int RGBA;            // Vertex color. RGBA
};
 
//Lump 11
//MeshVert
int nOffset;                        // Vertex index offset, relative to first
                                          // vertex of corresponding face. 
//Lump 12
//Effect
struct stEffect
{
      char cName[64];               // Effect shader. 
      int   nBrush;                // Brush that generated this effect. 
      int  Unknown;                 // Always 5, except in q3dm8, which has
                                          // one effect with -1. 
};
 

//Lump 13
//Faces
#pragma pack(1)
struct stFace // size 24*4
{
      int nShader;                  // Refers to a shader
      int nEffect;                  // Index into lump 12 (Effects), or -1
      int nFaceType;                // Face type. 1=polygon, 2=patch, 3=mesh, 4=billboard
      int FirstVert,
            NumVerts;               // Reference to vertices
 
      int nFirstMeshVerts,    // Index of first meshvert
                                          // Every three meshverts describe a triangle
            NumMeshVerts;           // Number of meshverts
 
      int LMIndex;                  // Lightmap index.
      int LMStart[2];               // X,Y Corner of this face's lightmap image in lightmap.
      int LMSize[2];                // Size of lightmap
 
      float LMOrigin[3];            // World space origin of lightmap.
      float LMVects[2][3];    // World space lightmap s and t unit vectors.
 
      Vector3 vNormal;        // Face normal
 
      int nSize[2];                 // Patch dimensions. 
};
#pragma pack()
 
 
//Lump 14
//Lightmaps
unsigned char pMap[128][128][3]; // Lightmap color data. RGB. 
 
//Lump 15
//Light Grid
//Unknown
 
//Lump 16
//Visibility Lists
struct stVisibility
{
      int NumVectors;               // Number of vectors.
      int nSizeVector;        // Size of each vector, in bytes
      unsigned char *pData;   // [NumVectors * nSizeVector]; 
                                          // Visibility data. One bit per cluster per vector
};
 
//Lump 17
//Number of Lumps
//Unknown
 
 
//-----------------------Write Debug Feedback Information-------------------------//
void abc(char *str)
{
      FILE *fp = fopen("dbg.txt", "a+");
      fprintf(fp, "%s", str);
      fclose(fp);
}
 
//-------------------------- Program Entry Point ---------------------------------//
void main()
{
      abc("<->Opening File\n\n");
 
      // Open BSP File
      FILE * fp = fopen(SZ_FILE_BSP, "rb");
     
      // Temporary Buffer
      stHeader Data;
      fread(&Data,                              // Buffer
              sizeof(stHeader),                 // Size
              1,                                // Number of times
              fp);                                    // File Pointer
 
      // Temp string buffer
      char buf[100];
      sprintf(buf, "<?>stHeader:cMagic = %c%c%c%c\n",
                         Data.cMagic[0],  // 'I'
                         Data.cMagic[1],  // 'B'
                         Data.cMagic[2],  // 'S'
                         Data.cMagic[3]); // 'P'
      abc(buf);
 
      sprintf(buf, "<?>stHeader:ver = 0x%X\n\n",
                         Data.nVersion);  // 0x2E for Quake3
      abc(buf);
 
      // Get the pointer to our array of lumps
      stLump * pLumps = Data.Lumps;
      // Display all the different types of Lumps
      for(int i=0; i<17; i++)
      {
           
            sprintf(buf, "<?>\tLump:(%d):%s"
                               "FileOffset: 0x%X"
                               "Length: 0x%X\n",
                           i+1,
                               szBSPTYPES[i],
                               pLumps[i].nFileofs,
                               pLumps[i].nFileLen);
            abc(buf);
      }
 
//------------------------------------Vertices------------------------------------//
      stLump * pLump = &pLumps[enVertices];
      fseek(      fp,
                  pLump->nFileofs,
                  SEEK_SET );
 
      char * pVData = new char[pLump->nFileLen];
 
      assert(pVData);
 
      fread(      pVData,                       // Buffer
                  1,                            // Size
                  pLump->nFileLen,  // Number of times
                  fp );                   // File Pointer
 
      stVertex * pVerts = 0;
      pVerts = (stVertex*)pVData;
 
      int nNumVerts = pLump->nFileLen / sizeof(stVertex);
 
      sprintf(buf, "\n<?>Num Vertices: %d\n", nNumVerts);
      abc(buf);
 
      for(i=0; i<nNumVerts; i++)
      {
            // We will only display some of the information
            sprintf(buf,"<?>Point:(%.2f,%.2f,%.2f) "
                              "Normal(%.2f,%.2f,%.2f) "
                              "RGBA(0x%X)\n",
                              pVerts[i].vPoint[0], // x
                              pVerts[i].vPoint[1], // y
                              pVerts[i].vPoint[2], // z
                              pVerts[i].vNormal[0],// nx
                              pVerts[i].vNormal[1],// ny
                              pVerts[i].vNormal[2],// nz
                              pVerts[i].RGBA );    // rgba
            abc(buf);
 
      }//End for i
 
      delete[] pVData;
 
//------------------------------------ Faces -------------------------------------//
      pLump = &pLumps[enFaces];
      fseek(      fp,
                  pLump->nFileofs,
                  SEEK_SET );
 
      char * pFData = new char[pLump->nFileLen];
 
      assert(pFData);
 
      fread(      pFData,                       // Buffer
                  pLump->nFileLen,  // Size
                  1,                            // Number of times
                  fp );                   // File Pointer
 
      stFace * pFaces = 0;
      pFaces = (stFace*)pFData;
 
      int nNumFaces = pLump->nFileLen / sizeof(stFace);
 
      // Display Information
      sprintf(buf, "\n<?>Num Faces: %d\n", nNumFaces);
      abc(buf);
 
      for(i=0; i<nNumFaces; i++)
      {
            // We will only display some of the information
            sprintf(buf,"<?>FirstVert(%d)  "
                              "NumVerts(%d) "
                              "FirstMeshVert(%d) "
                              "NumMeshVerts(%d) \n",
                              pFaces[i].FirstVert,
                              pFaces[i].NumVerts,
                              pFaces[i].nFirstMeshVerts,
                              pFaces[i].NumMeshVerts);
            abc(buf);
 
      }//End for i
 
      delete[] pFData;
 
      abc("\n<->Closing File\n");
      // Close BSP File
      fclose(fp);
}//End main()


Output Information (well most of it):


<->Opening File

<?>stHeader:cMagic = IBSP
<?>stHeader:ver = 0x2E

<?> Lump:(1):EntitiesFileOffset: 0x355FCLength: 0x948
<?> Lump:(2):TexturesFileOffset: 0x90Length: 0x318
<?> Lump:(3):PlanesFileOffset: 0x3A8Length: 0xD00
<?> Lump:(4):NodesFileOffset: 0x2308Length: 0xD80
<?> Lump:(5):LeavesFileOffset: 0x10A8Length: 0x1260
<?> Lump:(6):LeafFacesFileOffset: 0x49D8Length: 0x4F4
<?> Lump:(7):LeafBrushesFileOffset: 0x4ECCLength: 0x364
<?> Lump:(8):ModelsFileOffset: 0x5230Length: 0x28
<?> Lump:(9):BrushesFileOffset: 0x3088Length: 0x450
<?> Lump:(10):BrushSidesFileOffset: 0x34D8Length: 0x1500
<?> Lump:(11):VerticesFileOffset: 0x5258Length: 0x11D24
<?> Lump:(12):MeshIndicesFileOffset: 0x35F44Length: 0x34E0
<?> Lump:(13):EffectFileOffset: 0x35F44Length: 0x0
<?> Lump:(14):FacesFileOffset: 0x16F7CLength: 0x3AE8
<?> Lump:(15):LightmapsFileOffset: 0x1ABBCLength: 0x18000
<?> Lump:(16):LightVolsFileOffset: 0x32BBCLength: 0x2A40
<?> Lump:(17):VisDataFileOffset: 0x1AA64Length: 0x158

<?>Num Vertices: 1659
<?>Point:(-56.00,528.00,64.00) Normal(1.00,0.00,0.00) RGBA(0xFF020603)
<?>Point:(-56.00,528.00,128.00) Normal(0.71,0.00,-0.71) RGBA(0xFF030808)
<?>Point:(0.00,528.00,128.00) Normal(0.00,0.00,-1.00) RGBA(0xFF020000)
<?>Point:(56.00,528.00,128.00) Normal(-0.71,0.00,-0.71) RGBA(0xFF0D0304)
<?>Point:(56.00,528.00,64.00) Normal(-1.00,0.00,0.00) RGBA(0xFF030504)
<?>Point:(-56.00,456.00,64.00) Normal(1.00,0.00,0.00) RGBA(0xFF050D0E)
<?>Point:(-56.00,456.00,128.00) Normal(0.71,0.00,-0.71) RGBA(0xFF050D0E)
<?>Point:(0.00,456.00,128.00) Normal(0.00,0.00,-1.00) RGBA(0xFF090101)
<?>Point:(56.00,456.00,128.00) Normal(-0.71,0.00,-0.71) RGBA(0xFF0D0304)
<?>Point:(56.00,456.00,64.00) Normal(-1.00,0.00,0.00) RGBA(0xFF0E0505)
<?>Point:(-56.00,384.00,64.00) Normal(1.00,0.00,0.00) RGBA(0xFF071113)
<?>Point:(-56.00,384.00,128.00) Normal(0.71,0.00,-0.71) RGBA(0xFF050B0E)
<?>Point:(0.00,384.00,128.00) Normal(0.00,0.00,-1.00) RGBA(0xFF020608)
....

<?>Num Faces: 145
<?>FirstVert(0) NumVerts(15) FirstMeshVert(0) NumMeshVerts(0)
<?>FirstVert(15) NumVerts(15) FirstMeshVert(0) NumMeshVerts(6)
<?>FirstVert(30) NumVerts(15) FirstMeshVert(6) NumMeshVerts(6)
<?>FirstVert(45) NumVerts(15) FirstMeshVert(12) NumMeshVerts(6)
<?>FirstVert(60) NumVerts(15) FirstMeshVert(18) NumMeshVerts(6)
<?>FirstVert(75) NumVerts(15) FirstMeshVert(24) NumMeshVerts(0)
<?>FirstVert(90) NumVerts(15) FirstMeshVert(24) NumMeshVerts(0)
<?>FirstVert(105) NumVerts(15) FirstMeshVert(24) NumMeshVerts(6)
<?>FirstVert(120) NumVerts(15) FirstMeshVert(30) NumMeshVerts(6)
<?>FirstVert(135) NumVerts(15) FirstMeshVert(36) NumMeshVerts(0)
<?>FirstVert(150) NumVerts(15) FirstMeshVert(36) NumMeshVerts(6)
...

<->Closing File


Didn't want to put all the information here...as theres over a thousand vertices...so I just put '...' to represent thats theres more of it.



 
Advert (Support Website)

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