www.xbdev.net
xbdev - software development
Saturday April 26, 2025
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=0i<17i++)
      {
           
            
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):   TexturesFileOffset0x90Length0x318
<?> Lump:(3):   PlanesFileOffset: 0x3A8Length: 0xD00
<?Lump:(4):   NodesFileOffset0x2308Length0xD80
<?> Lump:(5):   LeavesFileOffset: 0x10A8Length: 0x1260
<?Lump:(6):   LeafFacesFileOffset0x49D8Length0x4F4
<?> Lump:(7):   LeafBrushesFileOffset: 0x4ECCLength: 0x364
<?Lump:(8):   ModelsFileOffset0x5230Length0x28
<?> Lump:(9):   BrushesFileOffset: 0x3088Length: 0x450
<?Lump:(10): BrushSidesFileOffset0x34D8Length0x1500
<?> Lump:(11): VerticesFileOffset: 0x5258Length: 0x11D24
<?Lump:(12): MeshIndicesFileOffset0x35F44Length0x34E0
<?> Lump:(13): EffectFileOffset: 0x35F44Length: 0x0
<?Lump:(14): FacesFileOffset0x16F7CLength0x3AE8
<?> Lump:(15): LightmapsFileOffset: 0x1ABBCLength: 0x18000
<?Lump:(16): LightVolsFileOffset0x32BBCLength0x2A40
<?> 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=0i<17i++)
      {
           
            
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=0i<nNumVertsi++)
      {
            
// 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=0i<nNumFacesi++)
      {
            
// 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):TexturesFileOffset0x90Length0x318
<?> Lump:(3):PlanesFileOffset: 0x3A8Length: 0xD00
<?Lump:(4):NodesFileOffset0x2308Length0xD80
<?> Lump:(5):LeavesFileOffset: 0x10A8Length: 0x1260
<?Lump:(6):LeafFacesFileOffset0x49D8Length0x4F4
<?> Lump:(7):LeafBrushesFileOffset: 0x4ECCLength: 0x364
<?Lump:(8):ModelsFileOffset0x5230Length0x28
<?> Lump:(9):BrushesFileOffset: 0x3088Length: 0x450
<?Lump:(10):BrushSidesFileOffset0x34D8Length0x1500
<?> Lump:(11):VerticesFileOffset: 0x5258Length: 0x11D24
<?Lump:(12):MeshIndicesFileOffset0x35F44Length0x34E0
<?> Lump:(13):EffectFileOffset: 0x35F44Length: 0x0
<?Lump:(14):FacesFileOffset0x16F7CLength0x3AE8
<?> Lump:(15):LightmapsFileOffset: 0x1ABBCLength: 0x18000
<?Lump:(16):LightVolsFileOffset0x32BBCLength0x2A40
<?> Lump:(17):VisDataFileOffset: 0x1AA64Length: 0x158

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

<?>Num Faces145
<?>FirstVert(0) NumVerts(15) FirstMeshVert(0) NumMeshVerts(0)
<?>FirstVert(15NumVerts(15FirstMeshVert(0NumMeshVerts(6)
<
?>FirstVert(30) NumVerts(15) FirstMeshVert(6) NumMeshVerts(6)
<?>FirstVert(45NumVerts(15FirstMeshVert(12NumMeshVerts(6)
<
?>FirstVert(60) NumVerts(15) FirstMeshVert(18) NumMeshVerts(6)
<?>FirstVert(75NumVerts(15FirstMeshVert(24NumMeshVerts(0)
<
?>FirstVert(90) NumVerts(15) FirstMeshVert(24) NumMeshVerts(0)
<?>FirstVert(105NumVerts(15FirstMeshVert(24NumMeshVerts(6)
<
?>FirstVert(120) NumVerts(15) FirstMeshVert(30) NumMeshVerts(6)
<?>FirstVert(135NumVerts(15FirstMeshVert(36NumMeshVerts(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-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.