BSP (
Binary Space Partitioning) is file format used by GoldSource to store map data. The name is based on BSP rendering and it is optimized for it. Documentation I found:
- hlbsp project
- Quake engine on en.Wikipedia.org
- idfs.io - BSP File Format
- Quake 1 BSP File Format
- Quake 2 BSP File Format
- Quake 3 BSP File Format
- Source (Half-Life 2) BSP File Format
I created my test map using
Valve Hammer Editor 3.4, Build 2363(for Counter-Strike 1.6).
Data
All data are Little Endian.Header
Header | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
4 |
byte[4] or unsigned int16 |
Version |
Version of BSP file.0x1E000000 = 30
|
4 |
int16 or unsigned int16 |
File Offset of Entities Lump | Entities Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Entities Lump | |
4 |
int16 or unsigned int16 |
File Offset of Planes Lump | Planes Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Planes Lump | |
4 |
int16 or unsigned int16 |
File Offset of Textures Lump | Textures Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Textures Lump | |
4 |
int16 or unsigned int16 |
File Offset of Vertices Lump | Vertices Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Vertices Lump | |
4 |
int16 or unsigned int16 |
File Offset of Visibility Lump | Visibility Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Visibility Lump | |
4 |
int16 or unsigned int16 |
File Offset of Nodes Lump | Nodes Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Nodes Lump | |
4 |
int16 or unsigned int16 |
File Offset of TextureInfo Lump | TextureInfo Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of TextureInfo Lump | |
4 |
int16 or unsigned int16 |
File Offset of Faces Lump | Faces Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Faces Lump | |
4 |
int16 or unsigned int16 |
File Offset of Lighting Lump | Lighting Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Lighting Lump | |
4 |
int16 or unsigned int16 |
File Offset of ClipNodes Lump | ClipNodes Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of ClipNodes Lump | |
4 |
int16 or unsigned int16 |
File Offset of Leaves Lump | Leaves Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Leaves Lump | |
4 |
int16 or unsigned int16 |
File Offset of MarkSurfaces Lump | MarkSurfaces Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of MarkSurfaces Lump | |
4 |
int16 or unsigned int16 |
File Offset of Edges Lump | Edges Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Edges Lump | |
4 |
int16 or unsigned int16 |
File Offset of SurfaceEdges Lump | SurfaceEdges Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of SurfaceEdges Lump | |
4 |
int16 or unsigned int16 |
File Offset of Models Lump | Models Lump . |
4 |
int16 or unsigned int16 |
Length in bytes of Models Lump |
#define LUMP_ENTITIES 0
#define LUMP_PLANES 1
#define LUMP_TEXTURES 2
#define LUMP_VERTICES 3
#define LUMP_VISIBILITY 4
#define LUMP_NODES 5
#define LUMP_TEXTURE_INFO 6
#define LUMP_FACES 7
#define LUMP_LIGHTING 8
#define LUMP_CLIP_NODES 9
#define LUMP_LEAVES 10
#define LUMP_MARKSURFACE 11
#define LUMP_EDGES 12
#define LUMP_SURFACE_EDGES 13
#define LUMP_MODELS 14
#define LUMPS_COUNT 15
typedef struct BSP_Lump_t
{
uint32_t Offset;
uint32_t Length;
} BSP_Lump;
typedef struct BSP_Header_t
{
unit8_t Version[4];
BSP_Lump Lumps[LUMPS_COUNT];
} BSP_Header;
Entities
ID = 0Entity lump is simple text lump.
1 byte = 1 character
Each line ends by 0x0A byte (
\A
).Example (from test map):
{
"wad" "\half-life\cstrike\chateau.wad;\half-life\cstrike\cs_bdog.wad;\half-life\valve\halflife.wad;"
"mapversion" "220"
"MaxRange" "4096"
"classname" "worldspawn"
"classname" "worldspawn"
}
{
"origin" "0 -256 64"
"angles" "0 90 0"
"classname" "info_player_start"
}
{
"origin" "0 256 64"
"angles" "0 270 0"
"classname" "info_player_deathmatch"
}
{
"origin" "0 0 192"
"_light" "255 255 128 200"
"classname" "light"
}
{
"origin" "0 -448 192"
"_light" "0 128 255 200"
"classname" "light"
}
{
"origin" "0 448 192"
"_light" "255 0 0 200"
"classname" "light"
}
{
"origin" "448 0 192"
"_light" "255 255 255 200"
"classname" "light"
}
{
"model" "*1"
"team" "2"
"classname" "func_buyzone"
}
{
"model" "*2"
"team" "1"
"classname" "func_buyzone"
}
{
"model" "*3"
"material" "1"
"health" "100"
"rendercolor" "0 0 0"
"spawnflags" "256"
"classname" "func_breakable"
}
Rules:
- Max Key Length = 32
- Max Value Length = 1024
-
All block must have
classname
key.
worldspawn
has (for some reason) 2. -
wad
address (inworldspawn
) starts with\half-life\
. -
model
always start with star character and then index into Models Lump. -
Middle value of
angles
is rotation aroundUp/Down
axis. -
origin
value is position of the entitiy.
For brush entity (withmodel
), the model is located around [0;0;0] and you must move it toorigin
position. -
team
fromfunc_buyzone
is:- Unasigned (All Players / Both Teams)
- Terrorist
- Counter-Terrorist
-
material
fromfunc_breakable
is:- Glass
- Wood
- Metal
- Flesh
- Cinder Block
- Ceiling Tile
- Computer
- Unbreakable Glass
- Rocks
-
spawnflags
isFlags
tab fromObject Properties
window (in Valve Hammer Editor).
The value (256
) must be read binary (0b00000000 0b000000001 0b00000000
in 4 bytes).
That is because I checkedInstant Crowbar
which is 1st row, 2nd column inFlags
. The flags are (in VHE) separated to 3 columns of 8 rows. -
_light
is property oflight
(andlight_environment
) with 4 numeric values separated by0x20
) characters (I also found onlylight
key or with only first 3 values).
First 3 values are 0-255 RGB components.
Last value is brightness. By default it is 200 (in Hammer Unit, 1 hu = 1/16 feet).
Planes
ID = 1Planes Lump is simple array of binary data.
Length of each structure are 20 bytes. That means this lump must have maximum length divisible by 20.
Planes Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
12 |
float[3] or Vector3D |
Normal | Plane's Normal Vector. |
4 | float | Distance |
Plane equation: Normal * X = Distance
|
4 |
int32_t or uint32_t |
Type |
Plane type.
Values 3-5 are non-axial and given axis is the nearest. Used by renderer to speed up some computations. |
typedef struct Vector3D_t
{
float X;
float Y;
float Z;
} Vector3D;
typedef struct BSP_Planes_Lump_t
{
Vector3D Normal;
float Distance;
uint32_t Type;
} BSP_Planes_Lump;
The structure defines the plane in 3-dimensional space using Hesse normal form.
Normal * Point - Distance = 0
Textures
ID = 2This lump is more complex then others.
It starts with header of offsets.
Textures Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
4 | uint32_t | Texture Count | Total number of textured (stored or referenced). |
Count * 4 |
int32_t[Count] or uint32_t[Count] |
Offsets for each texture | Offsets from start of this lump to each texture. |
typedef struct BSP_Textures_Lump_Header
{
uint32_t Count;
uint32_t* Offsets;
} BSP_Textures_Lump_Header;
On each offset is structure for the texture itself.
Textures Lump - Texture | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
16 |
char[16] or byte[16] |
Name | Name of this texture. |
4 |
int32_t or uint32_t |
Width | Width of this texture. |
4 |
int32_t or uint32_t |
Height | Height of this texture. |
8 |
int32_t[4] or uint32_t[4] |
MipMaps |
Offset to data of 4 MipMaps.
[0,0,0,0] for external textures (stored in WAD 3 files).
|
typedef struct BSP_Textures_Lump_Texture_t
{
char Name[16];
uint32_t Width;
uint32_t Height;
uint32_t MipMapOffsets[4];
} BSP_Textures_Lump_Texture;
If MipMap Offsets are not
[0,0,0,0]
, then they are relative to start of the texture structure. After last MipMap, there is palette of colors because textures are stored as byte array pointing to colors in the palette.typedef struct Color_24b_t
{
uint8_t Red;
uint8_t Green;
uint8_t Blue;
} Color_24b;
Color_24b Color[256];
Vertices
ID = 3Vertices Lump is simple array of Vector3D (3 float coordinates, 12 bytes).
The Lump must be divisible by 12.
typedef struct Vector3D_t
{
float X;
float Y;
float Z;
} Vector3D;
For converting into OBJ, I had to change coordinates to
[-x, z, y]
Visibility
ID = 4Borrowed from hlbsp project:
The VIS lump contains data, which is irrelevant to the actual BSP tree, but offers a way to boost up the speed of the renderer significantly. Especially complex maps profit from the use if this data. This lump contains the so-called Potentially Visible Sets (PVS) (also called VIS lists) in the same amout of leaves of the tree, the user can enter (often referred to as VisLeaves). The visiblilty lists are stored as sequences of bitfields, which are run-length encoded. Important: The generation of the VIS data is a very time consuming process (several hours) and also done by a seperate compiler. It can therefore be skipped when compiling the map, resulting in BSP files with no VIS data at all!
Nodes
ID = 5Simple array of structure. Total length is 14 bytes (and this lump must be divisible by 14 again).
Nodes Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
4 |
int32_t or uint32_t |
Plane | Index into Planes Lump |
2 |
int16_t or uint16_t |
1st Children Index |
If >0 then index into Nodes. If <0 then bit inverted (~ operator) into Leaves |
2 |
int16_t or uint16_t |
2nd Children Index | |
6 | int16_t[3] | AABB Min Coordinates | Coordinates of Axis Aligned Bounding Box. |
6 | int16_t[3] | AABB Max Coordinates | |
2 |
int16_t or uint16_t |
First Face |
Faces of this node. Start index and number of faces. |
2 |
int16_t or uint16_t |
Face Count |
TextureInfo
ID = 6TextureInfo Lump is referenced by Faces Lump and used to calculate UV Coordinates and Texture.
TextureInfo Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
12 |
float[3] or Vector3D |
Texture S Coordinates |
Used for calculating UV Coordinates. Formula from Source BSP File Format - Texinfo u = s.x * x + s.y * y + s.z * z + s_shift
v = t.x * x + t.y * y + t.z * z + t_shift x,y,z are coordinates of (build-time) position. |
4 | float | Texture S Shift | |
12 |
float[3] or Vector3D |
Texture T Coordinates | |
4 | float | Texture T Shift | |
4 |
int32_t or uint32_t |
Texture Index | Index into Texture in Textures Lump. |
4 |
int32_t or uint32_t |
Flags |
Texture Flags. Probably similar to Source BSP File Format - Texinfo. |
typedef struct BSP_TextureInfo_Lump_t
{
Vector3D S;
float S_Shift;
Vector3D T;
float T_Shift;
uint32_t Texture_Index;
uint32_t Flags;
} BSP_TextureInfo_Lump;
Faces
ID = 7...
Faces Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
2 |
int16_t or uint16_t |
Plane Index | Index into Planes Lump |
2 |
int16_t or uint16_t |
Plane Side |
Defines whenever is normal vector multiplied by -1 or not. 0 means not multiplied, otherwise multiplied by -1. I do not know why there are 2 bytes for 1bit value and only 16bit number for Plane Index when on other places, there is 32bit number. |
4 |
int32_t or uint32_t |
First SurfaceEdge | Indices into SurfaceEdges Lump. |
2 |
int16_t or uint16_t |
Number of SurfaceEdges | |
... |
Lighting
ID = 8Borrowed from hlbsp project:
This is one of the largest lumps in the BSP file. The lightmap lump stores all lightmaps used in the entire map. The lightmaps are arrays of triples of bytes (3 channel color, RGB) and stored continuously.I did not figured out how this should be used.
ClipNodes
ID = 9ClipNodes Lump is mostly similar to Nodes Lump but is used for collision detection. It is kept as simple as possible.
ClipNodes Lump | |||
---|---|---|---|
Size (bytes) | Data Type | Name | Description |
4 |
int32_t or uint32_t |
Plane Index | Index into Planes Lump |
2 |
int16_t or uint16_t |
1st Children Index | Similar to Nodes' Children. |
2 |
int16_t or uint16_t |
2nd Children Index |
Leaves
ID = 10...
MarkSurfaces
ID = 11...
Edges
ID = 12...
SurfaceEdges
ID = 13...
Models
ID = 14...