Unofficial Return to Castle Wolfenstein MDS File Format Spec v1.0

By Chris Cookson (cjcookson@hotmail.com)

Contents

Introduction

This document is my attempt at providing some documentation on the MDS format used in Return to Castle Wolfenstein. It’s totally unofficial, so bear with me if certain areas are a little vague or inaccurate. Most of the information was gleaned by myself using a hex editor and many nights of MAXScripting, but huge thanks go to Ryan Feltrin for providing me with the crucial last bits of the MDS puzzle; Richard Crowder for getting me to continue working on my MDS importer (as well as providing loads of help and advice); and TiCaL for all his help. Cheers, guys!

I’m assuming you’re familiar with the basics of 3D, Quake 3 mods, and skeletal animation with smooth mesh deformation, so I won’t cover these topics in much detail.

MDS In a Nutshell

So what’s in an MDS file? The main things are:

  • A skeleton, or hierarchy of bones, some of which represent tags (as in Quake 3)
  • A number of triangle meshes, known as surfaces
  • Animation frames in the form of skeletal poses
  • Runtime LOD data for each surface: the intriguingly named collapse maps

Tags are simply bones named with the special prefix “tag_” and work in a similar way to tags in Quake 3. In RTCW, they are mainly used as mount points for weapons, ammo packs, heads, mouths (for effects like cigarette smoke) and so on.

MDS Data Structures

These are the main data structures present in an MDS file:

I’ve used a C/C++ like notation with explicitly sized types for clarity. For example, uint32 is a 32-bit unsigned integer, sint16 is a signed 16-bit integer and so on. floats are assumed to be 32 bits in size, and chars 8-bits. Note that as there are some hard limits on the numbers of certain structures, so the unsigned/signed distinction is probably overkill.

MDSHeader
struct MDSHeader
{
  uint32  ident;          // "MDSW"
  uint32  version;        // 0x0004
  char    name[64];       // Model name eg. "body.mds"

  float   lodScale;       // LOD Scale
  float   lodBias;        // LOD Bias

  uint32  numFrames;      // Number of animation frames
  uint32  numBones;       // Number of bones
  uint32  offsetFrames;   // Offset of animation frames (see MDSFrame)
  uint32  offsetBones;    // Offset of bones (see MDSBone)

  uint32  torsoParent;    // Index of torso parent bone

  uint32  numSurfaces;    // Number of surfaces
  uint32  offsetSurfaces; // Offset of surfaces (numSurfaces * MDS_Surface)

  uint32  numTags;        // Number of tags
  uint32  offsetTags;     // Offset of tags (numTags * MDS_Tag)

  uint32  offsetEnd;      // Offset of end of file
};
This structure is located at the very start of an MDS file.

The torsoParent index probably refers to the bone that controls the overall orientation of a player’s torso.

MDSBone
struct MDSBone
{
  char   name[64];    // Bone name
  sint32 parentIndex; // Bone parent index (-1 if root)
  float  torsoWeight; // 0.0 to 1.0
  float  parentDist;  // Distance from parent bone to this bone's pivot point
  uint32 flags;       // Bit 0 is set if bone is a tag
};
Defines a bone used to deform the MDS model’s surface meshes during animation. The torsoWeight value is apparently used to scale the torso rotation and torso parent bone.
MDSTag
struct MDSTag
{
  char   name[64];    // Name of tag
  float  torsoWeight; // 0.0, 0.3
  uint32 boneIndex;   // Index of bone this tag is attached to
};
Defines a tag/attachment point for weapons, MD3/MDC head models and particle effects. Each tag has a corresponding bone in the skeleton, with the same name as the tag (eg. tag_head).
MDSFrame
struct MDSFrame
{
  float[3]  bboxMin;      // Bounding box min
  float[3]  bboxMax;      // Bounding box max
  float[3]  localOrigin;  // Local origin
  float     radius;       // Radius of bounding sphere (used for runtime culling)
  float[3]  parentOffset; // Offset of parent bone from origin

  // Following this structure is a list of compressed bone frames
  MDSCompressedBoneFrame  bones[numBones];
};
This structure is present at the start of every animation frame. Following each MDSFrame is a sequence of MDSCompressedBoneFrames, with one entry for each bone in the model’s skeleton.

The parentOffset vector defines the offset of the parent bone from the world origin.

MDSCompressedBoneFrame
struct MDSCompressedBoneFrame
{
  sint16[4]  angles;        // Defines the bone orientation
  sint16[2]  offsetAngles;  // Defines the direction of the bone pivot from this bone's parent
};
A bone keyframe that defines position and orientation per frame. The first 3 entries in the angles array are stored in (pitch, yaw, roll) order. The 4th entry is just padding and can be safely ignored (it’s typically set to zero anyway). The offsetAngles are stored in (pitch, yaw) order.

In RTCW, pitch, yaw and roll are defined assuming that +X is forward, -Y is right and +Z is up, with right handed rotations. For example, a positive pitch will tilt down towards -Z. To convert the compressed 16-bit angles into degrees, simply scale them by 360.0 / 65536.0.

To obtain the origin of the bone’s local coordinate system, create a rotation matrix from offsetAngles using its pitch and yaw values. Construct a 3D point with the parentDist value stored in the bone’s MDSBone structure as the x component, with y and z components set to zero (ie. [parentDist, 0, 0]). Transform this point using the offsetAngles rotation matrix and add it to the bone’s parent’s origin to get the bone’s own origin. If the bone has no parent, use the parentOffset coordinate stored in the MDSBoneFrame for this frame in place of the parent’s origin.

To correctly orient the bone, construct its rotation matrix from angles using the roll, pitch and yaw (in that order) values. Together with the bone origin, this rotation matrix defines the bone’s coordinate system for the current frame.

Take a look at MDSData.ms and MDSLoader.ms in my MDS Importer to see how I managed to load the bone information into gmax/max.

MDSSurface
struct MDSSurface
{
  uint32  ident;           // Always 0x0008
  char    name[64];        // Name of surface
  char    shader[64];      // Name of shader
  uint32  shaderIndex;     // Used in game only

  sint32  minLod;          // Minimum Lod level
  sint32  offsetHeader;    // Offset of this surface header, always negative

  uint32  numVerts;        // Number of vertices
  uint32  offsetVerts;     // Offset of vertices (MDSVertex * numVerts)

  uint32  numTris;         // Number of triangles
  uint32  offsetTris;      // Offset of triangles (MDSTriangle * numTris)

  uint32  offsetCollapseMap; // Offset of the collapse map (uint32 * numVerts in size)

  uint32  numBoneRefs;     // Number of bone references (bones that influence this surface)
  uint32  offsetBoneRefs;  // Offset of bone refs (uint32 * numBones)

  uint32  offsetEnd;       // Offset of the end of this surface, from start of MDSSurface header.
                           // The next surface (if there are more) can be found at this offset.
};
A surface is a triangle mesh rendered using a single shader. Each surface has its own collection of vertices (each influenced by one or more bones) and triangles. The bone references aren’t indexed by the MDSWeight structures, which instead index into the main bone list. The bone refs provide a list of all the bones referenced by a surface’s MDSWeight structures, which is often less than the total number of bones in the model.

The collapse map is a list of target vertices for each surface vertex. The mapping from a mesh vertex to a target vertex defines an edge collapse. By successively collapsing edges and removing triangles that become degenerate after these collapses, the surface can be simplified automatically. It’s also possible to go the other way, adding triangles right back up to the original full level of detail. This is known as a progressive mesh system and is used by RTCW to dynamically adjust the individual levels of detail for all onscreen MDS models whilst the game is running. See the collapse map section for a few more details on this dead funky technique.

The minLod is likely to be the lowest possible detail level. This is the point in the collapse map at which the engine should stop edge collapsing to prevent the mesh becoming noticeably distorted, even when in the distance.

MDSVertex
struct MDSVertex
{
  float[3]  normal;      // Vertex normal vector. In base pose/model space I presume.
  float[2]  texCoords;   // Texture (s,t) co-ordinates
  uint32    numWeights;  // Number of bone weights affecting this vertex

  uint32    fixedParent; // Stay equidistant from this parent
  float     fixedDist;   // Fixed distance from parent

  MDSWeight weights[numWeights]  // List of weights follows
};
A surface vertex, which must have at least one weight.

Not 100% sure on the fixedParent and fixedDist entries, but they are probably used to prevent certain vertices straying too far from certain bones (causing visually unacceptable distortion) during animation.

MDSWeight
struct MDSWeight
{
  uint32    boneIndex;  // Index of bone in the main bone list
  float     weight;     // Bone weighting (from 0.0 to 1.0)
  float[3]  xyz;        // xyz bone space position of vertex
};
There is an MDSWeight structure for every bone that has some influence over a vertex. Bones with higher weights have more influence over the final position of a vertex during animation. For example, a weight of 1.0 will mean the corresponding bone (indicated by boneIndex) totally defines the position of the vertex. The weight values stored in each MDSWeight for a single vertex should all sum to 1.0.

If you want to know more about how the smooth mesh deformation process works, check out Game Programming Gems I for some good articles. Also, have a look at the function “SkinBaseSurfaces” in the MDSLoader.ms MAXScript from my MDS Importer where I manually ‘skin’ the initial base pose mesh before handing it over to the Skin modifier to do the rest of the work.

MDSTriangle
struct MDSTriangle
{
  uint32[3] vertexIndices;  // 3 indices into the surface's vertex list
};
A triangle in a surface.
Limits

These are the upper limits imposed by RTCW on the numbers of various data structures in an MDS file:

  • Max no. verts = 6000
  • Max no. triangles = 8192
  • Max no. bones = 128
  • Max no. surfaces = 32
  • Max no. tags = 128

The Collapse Map

Here are some ramblings on the collapse map structure that might be of use:

The collapse map (as introduced here) is used for runtime dynamic LOD (level of detail) adjustment. In Quake 3, separate models where switched between according to the estimated on-screen size of each player model. The system used in Wolfenstein automatically adjusts the triangle count of the model up and down as its on-screen size changes without requiring any additional models! Cool, eh?

Models that can have their detail adjusted by edge collapses (and the opposite, vertex splits) are known as progressive meshes. Technically, RTCW uses view-independent progressive meshes or VIPM as it’s known to its friends. The particular variant used in RTCW is based on the one described by Stan Melax in his polygon reduction article, which you can find on his web site along with example programs and source.

There has been a lot written on progressive meshes in the last few years, and I’d recommend you consult Game Programming Gems I and II for some great articles by Jan Svarovsky and Tom Forsyth on the topic. Charles Bloom also has some very interesting VIPM notes on his web site. Finally, if you don’t mind academic papers, the relevant work by Hugues Hoppe is essential reading.

Creating the collapse map involves picking the edge collapse that causes the least visible difference, doing the collapse and repeating with the remaining edges until the mesh cannot be simplified any more. Once all the collapses are done, the mesh vertices are sorted into collapse order (last one to be collapsed is first) and the collapse map is written out. It would be far too time consuming to manually pick the edge collapses in the best order, so this process is done automatically by the MDS build tool. A fully featured exporter is free to choose edge collapses in any order, but for best results some time should be spent on developing a good error metric.

Polygon Counts

As a rough guide, here are total poly counts for some MDS models:

  • Helga: 1246
  • Trench: 1162
  • ProtoSolider: 2601
  • Venom: 1656
  • Multi: 1268

1250 tris seems about typical for an average player model.

The End


Copyright (C) 2002 Chris Cookson