MAP file format
file format spec + sample parser code for Quake .MAP files ("Valve" style, mapversion 220)
This is a technical file format specification article for programmers.
For mapping tutorials, see Quake resources instead.
The .MAP file format is a 3D file format devised for Quake 1 (1996).
It is gradually emerging as the standard file type for brush-based levels in any game engine, partly because it is relatively simple to parse and edit. It's similar to how .OBJ has become a simple 3D standard too, even though it lacks many advanced features.

Parsers

There's a good chance someone has already written a generic .MAP parser for your game engine / programming language.
For game engine-specific MAP importers and integrations, see TrenchBroom.

File format

  • Z axis is up (Quake convention), right-handed
  • angle values are Euler degrees from 0-359
    • -1 means "up"
    • -2 means "down"
    • some Quake mods implement rotations as a 3D vector called m_angle

Entities

Every object in a .MAP must be an entity, a generic actor / game object container type.
There are two types of entities:
  • point entities: monsters, items, lights, things with fixed size / no size
  • brush entities: point entities with 3D shapes embedded inside them
    • worldspawn: global static brush entity, the "root" of the entire game world
    • world brushes: brushes bound to worldspawn
Entity data and templates are defined by a .FGD file loaded into the level editor. (TODO: link to FGD format)
Entity schema
1
{
2
// entity data goes here
3
{
4
// (optional) brush data goes here
5
}
6
}
Copied!
Point entity
1
// a Point Entity is a curly brace scope
2
// with keyvalue pairs surrounded by quotation marks
3
{
4
// classname: the type of entity
5
"classname" "info_player_start"
6
7
// spawnflags: a bitmask of toggles for the entity
8
"spawnflags" "0"
9
10
// origin: the entity's position in 3D space
11
"origin" "32 32 24"
12
13
// but this is just Quake convention, you could put any arbitrary data here
14
}
Copied!
Brush entity
1
// a Brush Entity is like a Point Entity but with 3D brush definitions inside
2
{
3
"spawnflags" "0"
4
5
// note that classname is often NOT the first keyvalue pair
6
"classname" "worldspawn"
7
8
// a file path to a Quake WAD (texture set)
9
"wad" "E:\q1maps\Q.wad"
10
{
11
// 3D brush geometry information, see next section for details
12
( -16 -64 -16 ) ( -16 -63 -16 ) ( -16 -64 -15 ) __TB_empty [ 0 -1 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
13
( -64 -16 -16 ) ( -64 -16 -15 ) ( -63 -16 -16 ) __TB_empty [ 1 0 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
14
( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) __TB_empty [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
15
( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) __TB_empty [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
16
( 64 16 16 ) ( 65 16 16 ) ( 64 16 17 ) __TB_empty [ -1 0 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
17
( 16 64 16 ) ( 16 64 17 ) ( 16 65 16 ) __TB_empty [ 0 1 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
18
}
19
}
Copied!

Brush geometry

A brush is a convex 3D shape defined by 4 or more planes.
It is NOT a traditional 3D mesh format made of vertices and triangles. You need an additional map compile step to process the brush data into a usable 3D mesh. For example, Quake maps use an editor-time command line tool called QBSP that outputs a compiled .BSP map file; meanwhile many .MAP parsers for other engines instead generate this mesh at runtime.
Every brush line defines one 3D plane (a triangle) and its texture coordinates:
Brush plane
1
(x1 y1 z1) (x2 y2 z2) (x3 y3 z3) TEXTURE_NAME [ ux uy uz offsetX ] [ vx vy vz offsetY ] rotation scaleX scaleY
Copied!
Building the 3D mesh based on plane intersections is a bit complicated. Most libraries implement some variant on Stefan Hajnoczi's 2001 C++ implementation. His paper explains the math and process in detail, PDF download mirrored below:
MAPFiles_2001_StefanHajnoczi.pdf
244KB
PDF
uploaded from https://github.com/stefanha/map-files on 23 March 2022

Texturing / UVs

The Quake .MAP file format has two versions: the original Standard version, and an updated format by Valve Software.
The original Standard format had problems storing texture rotations. Given a 3D brush plane normal in XYZ, it discards the longest axis and stores the resulting Vector2. But if the plane is at a 45 degree angle, then the longest axis is now ambiguous and the map compiler must now arbitrarily favor an axis; floating point imprecision makes this preference even less predictable, often resulting in unpleasant texture stretching.
Standard texture coordinates:
TEXTURE_NAME offsetX offsetY rotation scaleX scaleY
Meanwhile, more recent Valve format .MAPs have the "mapversion" "220" key value set in the worldspawn. Valve format 220 resolves the 45 degree ambiguity case by storing UVs in Vector3 space, which also enables some useful UV skewing operations in TrenchBroom as well.
Valve format texture coordinates:
TEXTURE_NAME [ ux uy uz offsetX ] [ vx vy vz offsetY ] rotation scaleX scaleY
We generally recommend using Valve format, and there's no reason to use standard format other than backwards compatibility / porting old files to the updated format.
ValveFormatTexParse
1
// in the Valve texture format, parsing the file would yield these values in order:
2
// texname [ ux uy uz offsetX ] [ vx vy vz offsetY ] rotation scaleX scaleY
3
4
// per vertex and per face, coordinates can be calculated like this
5
6
Vec3 axisU = Vec3(ux, uy, uz) / scaleX;
7
Vec3 axisV = Vec3(vx, vy, vz) / scaleY;
8
9
for (FaceVertex& v : Vertices)
10
{
11
// Dot product of (v.Point, axisU)
12
v.TexU = v.Point.x * axisU.x + v.Point.y * axisU.y + v.Point.z * axisU.z;
13
// Dot product of (v.Point, axisV)
14
v.TexV = v.Point.x * axisV.x + v.Point.y * axisV.y + v.Point.z * axisV.z;
15
v.TexU += offsetX;
16
v.TexV += offsetY;
17
v.TexU /= Texture->GetWidth();
18
v.TexV /= Texture->GetHeight();
19
}
Copied!

Example .MAP file

For much more complex example .MAP files, see Quake resources.
1
// entity 0
2
{
3
"spawnflags" "0"
4
"classname" "worldspawn"
5
"wad" "E:\q1maps\Q.wad"
6
// brush 0
7
{
8
( -16 -64 -16 ) ( -16 -63 -16 ) ( -16 -64 -15 ) __TB_empty [ 0 -1 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
9
( -64 -16 -16 ) ( -64 -16 -15 ) ( -63 -16 -16 ) __TB_empty [ 1 0 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
10
( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) __TB_empty [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
11
( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) __TB_empty [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
12
( 64 16 16 ) ( 65 16 16 ) ( 64 16 17 ) __TB_empty [ -1 0 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
13
( 16 64 16 ) ( 16 64 17 ) ( 16 65 16 ) __TB_empty [ 0 1 0 -0 ] [ 0 0 -1 -0 ] -0 1 1
14
}
15
}
16
17
// entity 1
18
{
19
"spawnflags" "0"
20
"classname" "info_player_start"
21
"origin" "32 32 24"
22
}
Copied!

Sources