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; };
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 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]; }