Skip to content

StarCraft II M3 Model Format

M3 is the binary 3D model format used by StarCraft II (and Heroes of the Storm). OpenWarcraft3 includes an M3 loader so that StarCraft II assets can be rendered alongside Warcraft III content.

Overview

Unlike the chunked MDX format, an M3 file is structured as a reference table of typed data blocks. Every complex field in an M3 struct is a Reference — a (nEntries, offset, flags) triple that points into the reference table rather than embedding data inline. This makes the format highly self-describing and supports forward-compatibility through versioned structures.

File Header

DWORD  magic       // 'MD34' or '43DM' (little-endian)
DWORD  refOffset   // byte offset of the reference table
DWORD  refCount    // number of Reference entries
Reference modelRef // root model reference (always the first entry)

The file is divided into two regions: 1. Data region — packed binary data for all structs. 2. Reference table — array of { DWORD offset; DWORD nEntries; DWORD flags; } records immediately after the data region.

Core Types

typedef struct {
    DWORD  offset;   // byte offset into data region
    DWORD  nEntries; // number of elements
    DWORD  flags;    // usually 0
} Reference;

Primitive aliases used throughout the format:

Alias C type
m3Float32_t float
m3Vector2_t VECTOR2
m3Vector3_t VECTOR3
m3Vector4_t VECTOR4
m3Matrix4_t MATRIX4
m3Pixel_t COLOR32
m3Face_t USHORT

Animation References

Animated scalar and vector values are represented by AnimRef structs. Each AnimRef embeds the initial/null value directly and carries an animation ID referencing an animation sequence data block:

typedef struct {
    USHORT interpolationType; // 0=constant, 1=linear, 2=hermite, 3=bezier
    USHORT animFlags;
    DWORD  animId;            // index into ANIM table, or 0xFFFFFFFF
    <type> initValue;
    <type> nullValue;
    DWORD  unknown;
} m3<Type>AnimRef_t;

Typed variants are declared for Pixel, Uint16, Uint32, Float32, Vector2, Vector3, and Vector4.

Geometry

The geometry section contains:

  • Vertices — position, normal, tangent, UV coordinates, and up to 4 bone weights/indices per vertex.
  • Faces — triangle index list (m3Face_t = USHORT).
  • Bone lookup table — maps per-vertex bone indices to the skeleton's actual bone indices.

The renderer (renderer/m3/r_m3_load.c) reads these arrays and builds a VBO with the following per-vertex layout:

// From the GLSL vertex shader embedded in r_m3_load.c:
in vec3 i_position;
in vec4 i_color;
in vec2 i_texcoord;
in vec3 i_normal;
in vec4 i_skin1;        // bone indices (4 × uint8, packed in float4)
in vec4 i_boneWeight1;  // bone weights (4 × float)

Up to 64 bones are uploaded to the shader as a mat4 uBones[64] uniform array for GPU skinning.

Materials

M3 materials reference textures through a Reference to an array of m3Layer_t structs. Each layer includes:

  • a texture Reference (points to a Reference of path strings)
  • an AnimRef for UV offset and scale animation
  • flags controlling blending, UV wrapping, and normal-map usage

Bones and Animation

The skeleton is stored as an array of m3Bone_t structs, each containing:

  • parent bone index
  • initial transform (m3Matrix4_t)
  • AnimRefs for translation, rotation, scale
  • a bounding sphere used for culling

Animation sequences are stored as m3Sequence_t entries. Each sequence specifies: - start and end frame numbers - movement speed - bounding sphere for the whole sequence - a Reference to per-sequence data blocks (containing the actual keyframe arrays for every AnimRef in the model that changes in that sequence)

Bounding Volumes

Every node and sequence carries a BoundingSphere struct:

typedef struct {
    VECTOR3 min;
    VECTOR3 max;
    float   radius;
} BoundingSphere;

Collision shapes are defined separately as BoundingShape structs with a shape type (sphere / capsule / box), a reference bone, and a transform matrix.

Versioning

M3 structs carry an embedded version number. The reader uses a versioned macro M3_READ(buffer, var, version) that only reads a field if the buffer's struct version exceeds the given threshold:

#define M3_READ(BUFFER, VAR, VERSION) \
    if ((BUFFER)->ent.version > VERSION || VERSION == 0) \
        M3_Read(BUFFER, &VAR, sizeof(VAR));

This allows the same reader code to handle multiple revisions of the format.

Source Purpose
renderer/m3/r_m3.h All M3 struct definitions
renderer/m3/r_m3_load.c Reference-table reader, model loader, and GLSL shader