Skip to content

MDL: Half Life 1 file format

Marc-Antoine Lortie edited this page Dec 16, 2019 · 1 revision

The Half-Life 1 MDL file format.

Notes

  • Each position/rotation is in local space, except for the model's eye position, which is in model space.
  • The Half-Life engine uses a left handed coordinate system, where X is forward, Y is left and Z is up.
  • Rotations are represented as euler angles. The angles are such that pitch is the rotation around the Y axis, yaw is the rotation around the Z axis and roll is the rotation around the X axis.
  • The type vec3_t is an alias for a float[3] array. It is used to specify 3D positions or vectors.

Structures

Header

This is the header used when loading any MDL file. It provides global information about the model, as well as the offset to every other chunk.

The header has a size of 244 bytes.

Size (Bytes) Data Type Name Description
4 int id The magic word. See below.
4 int version The file format version. See below.
64 char[64] name Model name.
4 int length The file size in bytes.
12 vec3_t eyeposition The model's eye position.
12 vec3_t min Hull min extent.
12 vec3_t max Hull max extent.
12 vec3_t bbmin Clipping box min extent.
12 vec3_t bbmax Clipping box max extent.
4 int flags ?
4 int numbones The number of bones.
4 int boneindex The offset of the first bone chunk.
4 int numbonecontrollers The number of bone controllers.
4 int bonecontrollerindex The offset of the first bone controller chunk.
4 int numhitboxes The number of hitboxes.
4 int hitboxindex The offset of the first hitbox chunk.
4 int numseq The number of sequences.
4 int seqindex The offset of the first sequence chunk.
4 int numseqgroups The number of sequence groups.
4 int seqgroupindex The offset of the first sequence group chunk.
4 int numtextures The number of textures. See below.
4 int textureindex The offset of the first texture chunk.
4 int texturedataindex The offset of the pixel data of the first texture chunk.
4 int numskinref The number of replacable textures.
4 int numskinfamilies The number of skin families.
4 int skinindex The offset of the first skin value.
4 int numbodyparts The number of bodyparts.
4 int bodypartindex The offset of the first bodypart chunk.
4 int numattachments The number of attachments.
4 int attachmentindex The offset of the first attachment chunk.
4 int soundtable ?
4 int soundindex ?
4 int soundgroups ?
4 int soundgroupindex ?
4 int numtransitions The number of nodes in the sequence transition graph.
4 int transitionindex The offset of the first transition value of the sequence transition graph.

id should always be IDST for the main MDL file and external texture files.

version should always be 10. This is the version found in the official Half-Life SDK.

numtextures is the number of textures stored in the actual MDL file. If a model uses an external texture file, then this number will be 0 when reading from the main MDL file.

Note:

  • Sequence group files use a separate header for loading, but they can also be loaded with the following header.

Sequence header

Sequence group files use a separate header, but a regular header may also be used.

The Sequence header a size of 76 bytes.

Size (Bytes) Data Type Name Description
4 int id The magic word. See below.
4 int version The file format version. See below.
64 char[64] name Model name.
4 int length The file size in bytes.

id magic word should always be IDSQ for sequence group files.

version should always be 10. This is the version found in the official Half-Life SDK.

Texture

Textures.

Textures are stored as 8-bit color bitmaps. They are either stored in the main MDL file, or in an external MDL.

Each texture chunk has a size of 80 bytes. This does not include the size of the pixel data.

Size (Bytes) Data Type Name Description
64 char[64] name The texture name.
4 int flags One or more texture flags.
4 int width Width in pixel.
4 int height Height in pixel.
4 int index The offset of the pixel data.

Skins

A skin is a replacable texture. Skin information is stored as a matrix M x N where M is the number of skin families and N is the number of replacable textures. The number of replacable textures is the number of textures that can only be applied to a model at once.

Example

Given a model with 2 skin families and 6 replacable textures. We can see that the default skin uses textures 0 to 5. However, in the second skin family, texture 6 replaces the second texture, textures 7 replaces "before last" texture and texture 8 replaces last texture.

// First skin family.

[0][0] => 0
[0][1] => 1
[0][2] => 2
[0][3] => 3
[0][4] => 4
[0][5] => 5

// Second skin family.

[1][0] => 0
[1][1] => 6
[1][2] => 2
[1][3] => 3
[1][4] => 7
[1][5] => 8

Bone

Skeletal animation.

The bones are stored starting from the root node.

Each bone chunk has a size of 112 bytes.

Size (Bytes) Data Type Name Description
32 char[32] name The name of the bone.
4 int parent The bone's parent index.
4 int flags Unused.
24 int[6] bonecontroller See below.
24 float[6] value See below.
24 float[6] scale The scale value.

bonecontroller, value, scale all have a fixed size of 6 elements, or 6 slots. The 6 elements slots are related to the first 6 motion types.

Slot[0] <-> STUDIO_X  // pos x
Slot[1] <-> STUDIO_Y  // pos y
Slot[2] <-> STUDIO_Z  // pos z
Slot[3] <-> STUDIO_XR // rot x
Slot[4] <-> STUDIO_YR // rot y
Slot[5] <-> STUDIO_ZR // rot z

For each slot, bonecontroller stores the index of the bone controller, with respect to the motion bone used by this bone controller. If no bone controller is associated to the slot, then the value is -1.

Example

bonecontroller[0] => -1
bonecontroller[1] => bone controller at [1] has motion type STUDIO_Y.  
bonecontroller[2] => -1
bonecontroller[3] => -1
bonecontroller[4] => -1
bonecontroller[5] => bone controller at [5] has motion type STUDIO_ZR.

The first 3 slots of value make up the bone position and the last 3 slots make up the bone rotations.

The values in scale are used to scale the bone position and rotation, when reading a compressed animation value data.

Bone controller

Bone controllers are used in the game to change the position or the rotation of one or more bones at run time.

Each bone controller chunk has a size of 24 bytes.

Size (Bytes) Data Type Name Description
4 int bone The bone index.
4 int type The motion type. Can be used in conjunction with STUDIO_RLOOP.
4 float start The minimum value.
4 float end The maximum value.
4 int rest The value at rest.
4 int index See below.

index is the channel of the bone controller. The following example shows two bone controllers declared in a SMD. The index value for the first bone controller would be 0 and the second bone controller's index value would be 4.

$controller 0 "Head bone" XR -60.000000 60.000000  
$controller 4 "Mouth bone" ZR 0.000000 45.000000

Hitbox

Collision detection - Hitbox.

Each hitbox chunk has a size of 32 bytes.

Size (Bytes) Data Type Name Description
4 int bone The bone index.
4 int group The hit group.
12 vec3_t bbmin The hitbox min extent.
12 vec3_t bbmax The hitbox max extent.

Sequence group

Goldsrc allows animation data to be stored in separate files and load them on demand at run time. These files are called sequence group files.

Each sequence group data has a size of 104 bytes.

Size (Bytes) Data Type Name Description
32 char[32] label The sequence group name.
64 char[64] name The sequence group file name.
4 int unused1 Unused.
4 int unused2 Unused.

Note:

  • The sequence group file must be in the same directory as the main MDL file.
  • The file name is always prepended with "models/".

Sequence description

Skeletal animation.

Each sequence description chunk has a size of 176 bytes.

Size (Bytes) Data Type Name Description
32 char[32] label The sequence name.
4 float fps Frames per second.
4 int flags One or more sequence flags.
4 int activity The activity.
4 int actweight The activity weight.
4 int numevents The number of animation events.
4 int eventindex The offset of the first animation event chunk.
4 int numframes The number of frames.
4 int numpivots Unused.
4 int pivotindex Unused.
4 int motiontype The motion type.
4 int motionbone The motion bone index.
12 vec3_t linearmovement The motion bone's displacement from the first frame to the last frame of this sequence.
4 int automoveposindex Unused.
4 int automoveangleindex Unused.
12 vec3_t bbmin The sequence bounds min extent.
12 vec3_t bbmax The sequence bounds min extent.
4 int numblends The number of blend controllers.
4 int animindex The offset of the first animation frame offsets chunk.
8 int[2] blendtype The motion type. One per blend controller.
8 float[2] blendstart The start value. One per blend controller.
8 float[2] blendend The end value. One per blend controller.
4 int blendparent Unused.
4 int seqgroup The sequence group index.
4 int entrynode The entry value in the sequence transition graph.
4 int exitnode The exit value in the sequence transition graph.
4 int nodeflags One or more node flags.
4 int nextseq Unused.

actweight is the activity weight. Many sequences may use the same activity, but the game will pick the sequence that has the highest actweight.

The nodeflags is either 0 or 1, where 0 means that the transition to this sequence can only be made from entrynode to exitnode and 1 means the sequence transition can also be made in the reverse order, that is exitnode to entrynode.

Animations

An animation is used to tell how bones move. A sequence is used to group one or more animations coherently. Using more than 2 animations per sequences allows animation blending.

Animation blending

Blending refers to interpolating the bone positions and rotations from multiple animations. Goldsrc supports up to 4 blend animations, but you must chose either 2 or 4 animations for your blending. The reason is because of how the game interpolates between the animations. Blend controllers are used to determine the influence of each animation.

Blend controllers

The blend controllers specify the motion type using blendtype[2], as well as the range of each blend using blendstart[2] and blendend[2]. A sequence that has 2 animations will only need 1 blend controller, while a sequence that has 4 animations will use 2 blend controllers.

Here is an example that explains the influence of each blend controller.

Let A1, A2, A3, A4 be the 4 animations of the sequence, and s, t be the blend factor for the first and second blend controllers. Both s and t are between 0 and 1, inclusively. R is the animation resulting from the blending.

// Blend between the first and second animation using the first blend factor.
A = A1 * (1 - s) + A2 * s

// Blend between the third and fourth animation using the first blend factor.
B = A3 * (1 - s) + A4 * s

// Blend between A and B using the second blend factor.
R = A * (1 - t) + B * t

It is important to understand that the purpose of this example is to help understand the way blend controllers influence the resulting pose of the model. You cannot directly interpolate a bone's transformation matrix with another. Orientations need to use Quaternion slerp to interpolate correctly.

Animation frame offsets

Each sequence group file contains multiple chunks of this structure. The sequence property seqgroup tells which sequence group file contains the animation data of each animation in the sequence. For a given animation, there are as many chunks as there are bones. If a sequence contains more than one animation, then the next animation data can be accessed after the last chunk of the first animation .i.e they are stored contiguous. Each chunk stores 6 offsets (1 per motion type), to the first animation frame chunk of the current bone. The layout is as follows:

// Blend 1 (First animation)

Bone 0 chunk
Bone 1 chunk
Bone 2 chunk
...
Bone N chunk

// Blend 2 (Second animation)

Bone 0 chunk
Bone 1 chunk
Bone 2 chunk
...
Bone N chunk

// Blend 3 (Third animation)
...

// Blend 4 (Fourth animation)
...

Each anim chunk has a size of 12 bytes.

Size (Bytes) Data Type Name Description
12 unsigned short[6] offset The offset to the first animation frame chunk. One per element of the array. See below.

The offset has a fixed size of 6 elements, or 6 slots. The 6 elements slots are related to the first 6 motion types. Each value represents an offset to an animation frame chunk. If no animation frame is available for an offset, then the value is 0.

Animation frame

Animation compression

StudioMDL reduces animations by removing values of positions and rotations that are consecutive and identical.

For more info, please refer to the following links.

Structure

Each animation frame chunk has a maximum size of 2 bytes.

typedef union 
{
    struct {
        byte valid;
	byte total;
    } num;
    short value;
} mstudioanimvalue_t;
Size (Bytes) Data Type Name Description
1 uint8 valid ?
1 uint8 total ?
2 short value See below.

The value is one of the component X Y Z of either position or rotation at this frame.

Sequence transition graph

The sequence transition graph stores transitions between sequences. It is implemented similarly to an adjacency matrix. Each sequence contains an entrynode and an exitnode. The graph maps each entry node to the exit node, and each exit node to each entry node. The graph contains numtransitions * numtransitions entries where numtransitions is the parameter from header and represents the number of nodes.

Important: Entry node and exit nodes numbering starts at 1. You must subtract 1 from them to use them as matrix indices.

Given a sequence A, let A(a), A(b) be the entry and exit nodes from sequence A.

If no transition exists for a given relation, then the value at [A(a) - 1][A(b) - 1] is 0.

There are 2 types of transitions. Direct transitions and multistage transitions.

  • Direct transition means that there is a path to go from the entry node to the exit node directly .e.g [A(a) - 1][A(b) - 1] is A(b).

  • Multistage transition means that it is not possible to go from A(a) to A(b) directly, but there is a transitive relation such that A(a) => A(x) => A(b) where A(x) is an intermediate node and is equal to [A(a) - 1][A(b) - 1] . Therefore, the position of the intermediate node in the graph can be obtained via: [A(x) - 1][A(b) - 1].

Additionally, if the sequence has the nodeflags set to 1, then there is a path from A(b) to A(a). This means that [A(b) - 1][A(a) - 1] is A(a).

Animation event

Sequences may store events, which will be fired at a specific frame in the game at run time .e.g Playing a step sound each time the model's foot touches the ground.

Each animation event chunk has a size of 76 bytes.

Size (Bytes) Data Type Name Description
4 int frame The frame at which this event occurs.
4 int event The script event.
4 int type Unused.
64 char[64] options Additional data.

Attachment

Attachments are a position of interest on the model that can be retrieved at runtime in the game .e.g The gun tip from where to draw the muzzle flash. Each attachment will move accordingly to the bone it is parented to.

Each attachment chunk has a size of 88 bytes.

Size (Bytes) Data Type Name Description
32 char[32] name Unused.
4 int type Unused.
4 int bone The bone index.
12 vec3_t org The attachment position.
36 vec3_t[3] vectors Unused.

Bodypart

Bodyparts allow to group models coherently. For example, you could have multiple head models for a single bodypart named "heads" and change the head model in code at runtime.

Each bodypart chunk has a size of 76 bytes.

Size (Bytes) Data Type Name Description
64 char[64] name The name of the bodypart.
4 int nummodels The number of Models.
4 int base See below.
4 int modelindex The offset of the first model chunk.

base is the divisor used to calculate the index of a model relative to it's bodypart. In short, it is the process of converting a global model index to a local model index. Please refer to the code for an example.

Model

A model contains vertices, normals, and an index to the first mesh of this model.

Each model chunk has a size of 112 bytes.

Size (Bytes) Data Type Name Description
64 char[64] name The name of the model. This is also the name of the SMD file.
4 int type Unused.
4 float boundingradius Unused.
4 int nummesh The number of meshes.
4 int meshindex The offset of the first mesh chunk.
4 int numverts The number of vertex positions.
4 int vertinfoindex The offset to the first vertexinfoindex value.
4 int vertindex The offset to the first vertex position.
4 int numnorms The number of vertex normals.
4 int norminfoindex The offset to the first norminfoindex value.
4 int normindex The offset to the first vertex normal.
4 int numgroups Unused.
4 int groupindex Unused.
  • vertexinfoindex is the offset to an array of int. This array is the same size as numverts and maps each vertex position to a bone index. This bone index is used to tell which bone affects this vertex position.

  • norminfoindex is the offset to an array of int. This array is the same size as numnorms and maps each vertex normal to a bone index. This bone index is used to tell which bone affects this vertex normal.

Note:

  • The name of the model is actually the name of the SMD file that was used for this model.
  • Goldsrc does not support multiple bone weights per vertex, so each vertex is assigned to exactly one bone.

Mesh

Half-Life models store mesh triangles as strips or fans. Each mesh chunk specify a list of triverts. This list is divided in sequences and each new sequence means a different triangle type. Each sequence starts with the number of triverts N used in the sequence. If N is positive, this means that the sequence is a triangle strip, otherwise it is a triangle fan.

// Triangle strip
  
N <Trivert> <Trivert> <Trivert> ...

// Triangle fan 
 
-N <Trivert> <Trivert> <Trivert> ...  
  
...

Also, each mesh chunk stores the texture index to use for the mesh.

Each mesh chunk has a size of 20 bytes.

Size (Bytes) Data Type Name Description
4 int numtris The number of triverts.
4 int triindex The offset of the first trivert.
4 int skinref The texture index.
4 int numnorms Unused.
4 int normindex Unused.

Trivert

Triverts are used to specify which position, normal, texture coordinates to use for a single vertex.

Each trivert chunk has a size of 8 bytes.

Size (Bytes) Data Type Name Description
2 short vertindex The index of a vec3_t position. See below.
2 short normindex The index of a vec3_t normal. See below.
2 short s The horizontal texture coordinate. See below.
2 short t The vertical texture coordinate. See below.

vertindex is the index of a vec3_t position from the model vertex position array. See the model parameter named vertindex.

normindex is the index of a vec3_t normal from the model vertex normal array. See the model parameter named normindex.

The s and t coordinates are absolute, which means they are not normalized/unit coordinates. To get the normalized coordinate, you must divide both s and t by width and height of the texture of the mesh this trivert is associated with.

Note:

  • Two or more triverts can have the same vertindex, but not the same normindex, s, t.