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