Model and Animation File Formats

Legend of Grimrock 3D models are little endian binary files which contain a hierarchy of nodes. Each node has a transformation and a parent node (except the root node which has no parent). Optionally mesh entities and skeletons may be attached to nodes. Mesh entities are further split into segments. Each segment is a renderable triangle list which refers to a material by its name. Materials themselves are defined outside the model file in Lua script.

The simplest model file contains just a single node with a mesh attached and no skeleton. For example, items in the game are built like this. Monsters and other animated objects are more complex containing skeletons and skinning data.

In addition to model files, complex 3D objects such as monsters need animations. An animation file stores animation data for each bone in a model. However, separate animations such as “walk” and “attack” are stored in separate animation files.

The following basic data types are used in the animation and model files.

// A String is a variable length 8-bit string
struct String
{
    int32   length;
    byte    data[length];
}

// A FourCC is a four character code string used for headers
struct FourCC
{
    byte    data[4];
}

// A Vec3 is a 3D vector
struct Vec3
{
    float32 x, y, z;
}

// A Mat4x3 is a transformation matrix split into 3x3 rotation and 3D rotation parts
struct Mat4x3
{
    Vec3    baseX;
    Vec3    baseY;
    Vec3    baseZ;
    Vec3    translation;
}

// A Quaternion represents a rotation in 3D space
struct Quaternion
{
    float32 x, y, z, w;
};

Model Files

A model file simply contains a header and a variable number of nodes:

struct ModelFile
{
    FourCC  magic;           // "MDL1"
    int32   version;         // always two
    int32   numNodes;        // number of nodes following
    Node    nones[numNodes];
}

struct Node
{
    String  name;
    Mat4x3  localToParent;
    int32   parent;        // index of the parent node or -1 if no parent
    int32   type;          // -1 = no entity data, 0 = MeshEntity follows
    (MeshEntity)           // not present if type is -1    
}

The first node is the root node and its parent field must be set to -1. A Node may or may not contain a MeshEntity. Nodes without a mesh entity are used as bones for skeletal animation or as intermediate nodes in transformation chains.

struct MeshEntity
{
    MeshData meshdata;
    int32    numBones;         // number of bones for skeletal animation
    Bone     bones[numBones];
    Vec3     emissiveColor;    // deprecated, should be set to 0,0,0
    byte     castShadow;       // 0 = shadow casting off, 1 = shadow casting on
}

struct Bone
{
    int32 boneNodeIndex;    // index of the node used to deform the object
    Mat4x3 invRestMatrix;   // transform from model space to bone space
}

struct MeshData
{
    FourCC      magic;          // "MESH"
    int32       version;        // must be two
    int32       numVertices;    // number of vertices following
    VertexArray vertexArrays[15];
    int32       numIndices;     // number of triangle indices following
    int32       indices[numIndices];
    int32       numSegments;    // number of mesh segments following
    MeshSegment segments[numSegments];
    Vec3        boundCenter;    // center of the bound sphere in model space
    float32     boundRadius;    // radius of the bound sphere in model space
    Vec3        boundMin;       // minimum extents of the bound box in model space
    Vec3        boundMax;       // maximum extents of the bound box in model space
}

MeshData struct contains the vertices, index lists (also called triangle lists), mesh segments and bounding volumes of a mesh. Historically meshes used to be stored separately from models, so MeshData has its own header and versioning information.

The vertex data is split into vertex arrays. Up to 15 vertex arrays may be used. The vertex array indices are:

VERTEX_ARRAY_POSITION = 0
VERTEX_ARRAY_NORMAL = 1
VERTEX_ARRAY_TANGENT = 2
VERTEX_ARRAY_BITANGENT = 3    
VERTEX_ARRAY_COLOR = 4
VERTEX_ARRAY_TEXCOORD0 = 5
VERTEX_ARRAY_TEXCOORD1 = 6
VERTEX_ARRAY_TEXCOORD2 = 7
VERTEX_ARRAY_TEXCOORD3 = 8
VERTEX_ARRAY_TEXCOORD4 = 9
VERTEX_ARRAY_TEXCOORD5 = 10
VERTEX_ARRAY_TEXCOORD6 = 11
VERTEX_ARRAY_TEXCOORD7 = 12
VERTEX_ARRAY_BONE_INDEX = 13
VERTEX_ARRAY_BONE_WEIGHT = 14

Unused vertex arrays should have its dataType, dim and stride fields set to zero. Tangent and binormal vertex arrays are only used for normal mapped models. Position, normal, tangent and bitangent vertex arrays contain three dimensional data and should have its dim field set to 3. Texcoords are two dimensional data and bone indices and weights are four dimensional data. Bone indices and weights should use the byte data type while all other vertex arrays should use the float32 data type. Vertex colors are currently unsupported.

struct VertexArray
{
    int32 dataType;   // 0 = byte, 1 = int16, 2 = int32, 3 = float32
    int32 dim;        // dimensions of the data type (2-4)
    int32 stride;     // byte offset from vertex to vertex
    byte  rawVertexData[numVertices*stride];
}

Finally MeshSegment defines a contiguous segment of the index list that is rendered with a given material.

struct MeshSegment
{
    String material;      // name of the material defined in Lua script
    int32  primitiveType; // always two
    int32  firstIndex;    // starting location in the index list
    int32  count;         // number of triangles
}

Animation Files

Animation files contain a header and a number of animated items. Each item is bound to a node in a model file at runtime. Animation data is stored as keyframes which are linearly interpolated. Typically animation data is stored at 30 frames per second. Due to nature of the interpolation quaternion data must be preprocessed so that the interpolation follows the shortest arc in quaternion space.

Animation data consists of three channels: position, rotation and scaling. For many animations not all channels are needed. For example most animations do not have scaling. For absent channels, a single key frame is needed to store the constant value.

struct AnimationFile
{
    FourCC  magic;           // "ANIM"
    int32   version;         // always two
    String  name;            // name of the animation
    float32 framesPerSecond;
    int32   numFrames;       // length of the animation in frames
    int32   numItems;        // number of animation items following
    Item    items[numItems];
}

struct Item
{
    String     node;             // name of the node to be animated
    int32      numPositionKeys;  // number of position key frames following
    Vec3       positionkeys[numPositionKeys];
    int32      numRotationKeys;  // number of rotation key frames following
    Quaternion rotationKeys[numRotationKeys];
    int32      numScaleKeys;     // number of scale key frame following
    Vec3       scale[numScaleKeys];
}