MD2 has no written specification. Its definition is the header file
qfiles.h in the Quake II source code, which id Software released under the
GPL in 2001, together with the loader in the renderer. This page follows those two. Every
number is little-endian: 32-bit integers and floats, 16-bit integers where noted.
Header
The header is 17 32-bit integers, 68 bytes, at the start of the file. All offsets are counted in bytes from the start of the file.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | char[4] | ident | IDP2 (49 44 50 32), 0x32504449 read as an integer |
| 4 | 4 | int32 | version | Always 8; the game refuses any other value |
| 8 | 4 | int32 | skinwidth | Skin width in pixels |
| 12 | 4 | int32 | skinheight | Skin height in pixels; the game refuses more than 480 |
| 16 | 4 | int32 | framesize | Size of one frame in bytes, normally 40 + 4 × num_xyz |
| 20 | 4 | int32 | num_skins | Number of skin names, at most 32 |
| 24 | 4 | int32 | num_xyz | Vertices in each frame, 1 to 2048 |
| 28 | 4 | int32 | num_st | Texture coordinates, at least 1; often more than num_xyz, because a vertex on a seam needs two |
| 32 | 4 | int32 | num_tris | Triangles, 1 to 4096 |
| 36 | 4 | int32 | num_glcmds | Length of the OpenGL command list in 32-bit words, not in commands |
| 40 | 4 | int32 | num_frames | Animation frames, 1 to 512 |
| 44 | 4 | int32 | ofs_skins | Offset of the skin names |
| 48 | 4 | int32 | ofs_st | Offset of the texture coordinates |
| 52 | 4 | int32 | ofs_tris | Offset of the triangles |
| 56 | 4 | int32 | ofs_frames | Offset of the first frame |
| 60 | 4 | int32 | ofs_glcmds | Offset of the OpenGL commands |
| 64 | 4 | int32 | ofs_end | Offset of the end of the file, which is the file’s size |
The limits come from the constants in qfiles.h:
MAX_TRIANGLES 4096, MAX_VERTS 2048, MAX_FRAMES
512, MAX_MD2SKINS 32 and MAX_SKINNAME 64. The loader stops with
an error when the version is not 8, the skin is taller than 480 pixels, there are no
vertices or more than 2048, or there are no texture coordinates, triangles or frames.
Because each list is found through its own offset, the lists may be stored in any
order; id’s tools write them in the order of the sections below.
49 44 50 32 08 00 00 00 ...
I D P 2 version 8
Skins
num_skins entries of 64 bytes at ofs_skins. Each is a path
to an image, relative to the game folder and padded with zero bytes, such as
models/monsters/tank/skin.pcx. Quake II’s skins are PCX files.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 64 | char[64] | name | Image path, ended by a zero byte |
Texture coordinates
num_st entries of 4 bytes at ofs_st
(dstvert_t). They are in skin pixels, so a reader divides
s by skinwidth and t by
skinheight to get values from 0 to 1. Quake II’s software renderer uses
these; its OpenGL renderer uses the coordinates in the OpenGL commands instead.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 2 | int16 | s | Horizontal position in the skin, in pixels |
| 2 | 2 | int16 | t | Vertical position in the skin, in pixels, from the top |
Triangles
num_tris entries of 12 bytes at ofs_tris
(dtriangle_t). Each corner names a vertex and, separately, a texture
coordinate, so one vertex can take different skin positions in different
triangles.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 6 | int16[3] | index_xyz | The three vertices, numbered from 0 within each frame |
| 6 | 6 | int16[3] | index_st | The three texture coordinates, numbered from 0 |
Frames
num_frames frames, one after another from ofs_frames, each
framesize bytes long (daliasframe_t). A frame holds a
complete pose of the model.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 12 | float32[3] | scale | Multiplier for each packed coordinate, x, y, z |
| 12 | 12 | float32[3] | translate | Added after scaling, x, y, z |
| 24 | 16 | char[16] | name | Frame name, such as stand01 or run3, ended by a zero byte |
| 40 | 4 × num_xyz | vertex[] | verts | Every vertex of the pose, in the format below |
The file has no list of animations. The game code plays ranges of frame numbers, and viewers usually group frames by the name before its trailing digits.
Packed vertex
Each vertex (dtrivertx_t) is 4 bytes. The position of coordinate
i is v[i] × scale[i] + translate[i], so each frame’s vertices sit
on a grid of 256 steps across the frame’s bounding box.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 3 | uint8[3] | v | Packed x, y, z, each 0 to 255 |
| 3 | 1 | uint8 | lightnormalindex | The vertex normal, as an index from 0 to 161 into the normal table |
Worked example: a frame with scale 0.5, 0.5, 0.5 and translate −32, −32, −16 holds the
vertex 40 C0 20 05. Its position is 64 × 0.5 − 32 = 0, 192 × 0.5 − 32 =
64 and 32 × 0.5 − 16 = 0, and normal 5 is (0, 0, 1), straight up.
Quake’s axes are x forward, y left and z up. The game’s models face along +x.
OpenGL commands
num_glcmds 32-bit words at ofs_glcmds. They redraw the
triangles as strips and fans, with texture coordinates already scaled to 0 to 1, so
the OpenGL renderer can send them straight to the graphics card. The list is a
sequence of commands:
| First word | Meaning | Followed by |
|---|---|---|
| Positive n | A triangle strip | n vertices |
| Negative n | A triangle fan | −n vertices |
| 0 | End of the list | Nothing |
Each vertex in a command is 12 bytes:
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | float32 | s | Horizontal texture coordinate, 0 to 1 |
| 4 | 4 | float32 | t | Vertical texture coordinate, 0 to 1 |
| 8 | 4 | int32 | index | Vertex number within each frame |
So a strip of three vertices takes 1 + 3 × 3 = 10 words. A reader stops at the zero
word rather than relying on num_glcmds alone.
FD FF FF FF fan of 3 vertices (−3)
00 00 00 3F 00 00 80 3E 00 00 00 00 s 0.5, t 0.25, vertex 0
... two more vertices
00 00 00 00 end of the list