MDX

From wowdev
Jump to: navigation, search
This section only applies to versions < Vanilla.

MDX files are chunked binary files that contain model objects. They are the predecessor of the M2 format.

First used in Warcraft 3, MDX was actively developed and used in WoW as the primary model format until patch PreVanilla (0.11.0.3925). Although obsolete, some DBCs still contain filenames with a .mdx extension.

Note: The majority of the below information has been taken from the PreVanilla (0.5.3.3368) client and is only truly compliant for version 1300 of the format.

Structure

The complete structure for a MDX file. Note: Chunks after MODL don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the K*** sub-chunks.

struct MDLBASE
{
  char magic[4];           // MDLX
  VERS version;
  MODL model; 
  SEQS sequences;
  GLBS globalSeqs;
  MTLS materials;
  TEXS textures;
  TXAN textureanims;
  GEOS geosets;
  GEOA geosetAnims;
  BONE bones;
  LITE lights;
  HELP helpers;
  ATCH attachments;
  PIVT pivotPoints;
  PREM particleEmitters;
  CAMS cameras;
  EVTS events;
  PRE2 particleEmitters2;
  HTST hitTestShapes;
  RIBB ribbonEmitters;
  CLID collision;
};

Common Types

C3Color

struct C3Color
{
  float b;
  float g;
  float r;
};

C4QuaternionCompressed

For the WoW variant of MDX, all C4Quaternionⁱs are packed in int64_ts.

struct C4QuaternionCompressed
{
  int64_t m_data;
 
  C4Quaternion Get()
  {
     C4Quaternion result;	  
     result.X = ( m_data >> 42 ) * 0.00000047683716;
     result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;
     result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;
     result.W = GetW( result.X, result.Y, result.Z );      
     return result;
  }
  
  double GetW(float x, float y, float z)
  {
     double len = x * x + y * y + z * z;      
     if (( 1.0 - len ) >= 0.00000095367432 )
        return sqrt( 1.0 - len );
     
     return 0.0;
  }
  
  void Set(C4Quaternion source)
  {
     int32_t sign = ( source->W >= 0.0 ? 1 : -1 );            
     int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;
     int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;
     int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;
     
     m_data = z | (( y | x ) << 21 );
  }    
};

CMdlBounds

struct CMdlBounds
{
  CAaBox extent;
  float radius;
};

MDLKEYTRACK

The MDLKEYTRACK is a simpler equivalent of the M2Track. It stores a list of MDLKEYFRAMES which are essentially tuples of a time and T type values. If the type is TRACK_HERMITE or TRACK_BEZIER then the frame also contains inTan and outTan information. See the relevant M2 interpolation section for more information.

Under certain conditions the client overrides the track type e.g. when MDLMODELSECTION.flags & 4 (always animate) is set TRACK_LINEAR is used.

template<typename T>
struct MDLKEYTRACK<T>
{
  uint32_t count;
  MDLTRACKTYPE type;
  uint32_t globalSeqId;        // GLBS index or 0xFFFFFFFF if none
  MDLKEYFRAME<T> keys[count];  
};

template<typename T>
struct MDLKEYFRAME<T>
{
  int32_t time;
  T value;
 #if MDLKEYTRACK.type > TRACK_LINEAR
  T inTan;
  T outTan;
 #endif
};

enum MDLTRACKTYPE : uint32_t
{
  TRACK_NO_INTERP = 0x0,
  TRACK_LINEAR = 0x1,
  TRACK_HERMITE = 0x2,
  TRACK_BEZIER = 0x3,
  NUM_TRACK_TYPES = 0x4,
};

MDLSIMPLEKEYTRACK

The MDLSIMPLEKEYTRACK is used in place of the MDLKEYTRACK when only linear integer values are required. Types used by this track are MDLINTKEY and MDLEVENTKEY.

template<typename T>
struct MDLSIMPLEKEYTRACK<T>
{
  uint32_t count;
  uint32_t globalSeqId;  // GLBS index or 0xFFFFFFFF if none
  T keys[count];
};

struct MDLINTKEY         // default type
{
  uint32_t time;
  uint32_t value;
};

struct MDLEVENTKEY       // only used for the EVTS KEVT sub-chunk
{
  int32_t time;
};

MDLGENOBJECT

MDLGENOBJECT is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.

The hierarchy is usually organised as: Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes. The client will throw an exception if the objectIds are not sequential.

struct MDLGENOBJECT
{
  uint32_t size;
  char name[0x50];
  uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into PIVT
  uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none
  uint32_t flags;
  
  KGTR transkeys;
  KGRT rotkeys;
  KGSC scalekeys;
};

Flags

Notes: Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to PRE2 objects. GENOBJECT flags are also set in the class constructor.

Flag Meaning Notes
0x00000001 DONT_INHERIT_TRANSLATION
0x00000002 DONT_INHERIT_SCALING
0x00000004 DONT_INHERIT_ROTATION
0x00000008 BILLBOARD
0x00000010 BILLBOARD_LOCK_X
0x00000020 BILLBOARD_LOCK_Y
0x00000040 BILLBOARD_LOCK_Z
0x00000080 GENOBJECT_MDLBONESECTION not explicitly set in the files however all other GENOBJECT flags are
0x00000100 GENOBJECT_MDLLIGHTSECTION
0x00000200 GENOBJECT_MDLEVENTSECTION
0x00000400 GENOBJECT_MDLATTACHMENTSECTION
0x00000800 GENOBJECT_MDLPARTICLEEMITTER2
0x00001000 GENOBJECT_MDLHITTESTSHAPE
0x00002000 GENOBJECT_MDLRIBBONEMITTER
0x00004000 PROJECT
0x00008000 EMITTER_USES_TGA (PREM), UNSHADED (PRE2) UNSHADED disables lighting on particle materials
0x00010000 EMITTER_USES_MDL (PREM), SORT_PRIMITIVES_FAR_Z (PRE2)
0x00020000 LINE_EMITTER
0x00040000 PARTICLE_UNFOGGED disables fog on particle materials
0x00080000 PARTICLE_USE_MODEL_SPACE uses model space instead of world space
0x00100000 PARTICLE_INHERIT_SCALE
0x00200000 PARTICLE_INSTANT_VELOCITY_LIN ivelScale is applied, see PRE2
0x00400000 PARTICLE_0XKILL particles are destroyed after their first update tick
0x00800000 PARTICLE_Z_VELOCITY_ONLY particle X and Y velocities are set to 0.0 at instantiation
0x01000000 PARTICLE_TUMBLER unused by the client 
0x02000000 PARTICLE_TAIL_GROWS
0x04000000 PARTICLE_EXTRUDE extrudes between the previous and current translation
0x08000000 PARTICLE_XYQUADS particles align to the XY axis facing the Z axis
0x10000000 PARTICLE_PROJECT
0x20000000 PARTICLE_FOLLOW particles follow each other

KGTR

Geoset translation track

struct KGTR
{
  char tag[4]; // KGTR
  MDLKEYTRACK<C3Vector> transkeys;
};

KGRT

Geoset rotation track

struct KGRT
{
  char tag[4]; // KGRT
  MDLKEYTRACK<C4QuaternionCompressed> rotkeys;
};

KGSC

Geoset scale track

struct KGSC
{
  char tag[4]; // KGSC
  MDLKEYTRACK<C3Vector> scalekeys;
};

VERS

Version. Equivalent to the MVER chunk.

File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.

v1500 sees two structural changes from the previous iterations namely; new flags in the MTLS chunk and a complete redesign of the GEOS chunk.

uint32_t version; // 800 WC3, 1300 < PreVanilla (0.9.1.3810), 1400 & 1500 ≥ PreVanilla (0.9.1.3810)

MODL

Global model information.

struct MDLMODELSECTION
{
  char name[0x50];
  char animationFile[0x104];  // always 0 filled
  CMdlBounds bounds;
  uint32_t blendTime;
  uint8_t flags;              // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate
};

enum GROUND_TRACK
{
  TRACK_YAW_ONLY = 0x0,
  TRACK_PITCH_YAW = 0x1,
  TRACK_PITCH_YAW_ROLL = 0x2,
  GROUND_TRACK_MASK = 0x3,
};

SEQS

Sequences. MDX uses a single track for all animations meaning start times and end times between each animation are consecutive.

struct SEQS
{
   uint32_t numSeqs;   // limited to 0xFF
   MDLSEQUENCESSECTION sequences [numSeqs];
};
 
struct MDLSEQUENCESSECTION
{
  char name[0x50];
  CiRange time;       // start time, end time
  float movespeed;     // movement speed of the entity while playing this animation
  uint32_t flags;      // &1: non looping
  CMdlBounds bounds;
  float frequency;     // determines chance of this animation playing. for all animations of the same type this must add to 1.0
  CiRange replay;     // the client will pick a random number of repetitions within bounds
  uint32_t blendTime;
};

GLBS

Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.

struct MDLGLOBALSEQSECTION
{
  uint32_t length[chunk.size / 0x4];
};

MTLS

Materials.

struct MTLS
{
  uint32_t numMaterials;    // limited to 0xFF
  uint32_t unused;          // has values but is ignored by the client
  MDLMATERIALSECTION materials[numMaterials];
};

struct MDLMATERIALSECTION
{
  uint32_t size;
  int32_t priorityPlane;    // priority is sorted lowest to highest
  uint32_t numLayers;	     
  MDLTEXLAYER texLayers[numLayers];	
};

struct MDLTEXLAYER
{ 
  uint32_t size;
  MDLTEXOP blendMode;
  MDLGEO flags;
  uint32_t textureId;        // TEXS index or 0xFFFFFFFF for none
  uint32_t transformId;      // TXAN index or 0xFFFFFFFF for none
  uint32_t coordId;          // shader property
  float staticAlpha;         // 0 for transparent, 1 for opaque
  
  KMTA alphaKeys;
  KMTF flipKeys;
};

enum MDLTEXOP : uint32_t
{
  TEXOP_LOAD = 0x0,
  TEXOP_TRANSPARENT = 0x1,
  TEXOP_BLEND = 0x2,
  TEXOP_ADD = 0x3,
  TEXOP_ADD_ALPHA = 0x4,
  TEXOP_MODULATE = 0x5,
  TEXOP_MODULATE2X = 0x6,
  NUMTEXOPS = 0x7,
};

enum MDLGEO : uint32_t
{
  MODEL_GEO_UNSHADED = 0x1,
  MODEL_GEO_SPHERE_ENV_MAP = 0x2,  // unused until v1500
  MODEL_GEO_WRAPWIDTH = 0x4,       // unused until v1500
  MODEL_GEO_WRAPHEIGHT = 0x8,      // unused until v1500
  MODEL_GEO_TWOSIDED = 0x10,
  MODEL_GEO_UNFOGGED = 0x20,
  MODEL_GEO_NO_DEPTH_TEST = 0x40,
  MODEL_GEO_NO_DEPTH_SET = 0x80,
  MODEL_GEO_UNKNOWN_1500 = 0x100,  // added in v1500. seen in ElwynnTallWaterfall01.mdx, FelwoodTallWaterfall01.mdx and LavaFallsBlackRock*.mdx
};

KMTA

Material alpha track

struct KMTA
{
  char tag[4]; // KMTA
  MDLKEYTRACK<float> alphaKeys;
};

KMTF

Material flipbook texture track

struct KMTF
{
  char tag[4]; // KMTF
  MDLSIMPLEKEYTRACK<MDLINTKEY> flipKeys;
};

TEXS

Textures. The client reads MDLTEXTURESECTIONs until chunk.size bytes have been read.

struct TEXS
{
  MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];
};

struct MDLTEXTURESECTION
{
  REPLACEABLE_MATERIAL_IDS replaceableId;   // used for texture variations or 0 for none
  char image[0x104];                        // 0 filled when replaceableId is set
  uint32_t flags;                           // &1: wrap width, &2: wrap height
};

enum REPLACEABLE_MATERIAL_IDS : uint32_t
{
  TEX_COMPONENT_SKIN = 0x1,
  TEX_COMPONENT_OBJECT_SKIN = 0x2,
  TEX_COMPONENT_WEAPON_BLADE = 0x3,
  TEX_COMPONENT_WEAPON_HANDLE = 0x4,
  TEX_COMPONENT_ENVIRONMENT = 0x5,
  TEX_COMPONENT_CHAR_HAIR = 0x6,
  TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,
  TEX_COMPONENT_SKIN_EXTRA = 0x8,
  TEX_COMPONENT_UI_SKIN = 0x9,
  TEX_COMPONENT_TAUREN_MANE = 0xA,
  TEX_COMPONENT_MONSTER_1 = 0xB,
  TEX_COMPONENT_MONSTER_2 = 0xC,
  TEX_COMPONENT_MONSTER_3 = 0xD,
  TEX_COMPONENT_ITEM_ICON = 0xE,
  NUM_REPLACEABLE_MATERIAL_IDS = 0xF,
};

TXAN

Texture Animations.

struct TXAN
{
  uint32_t numTexAnims;
  MDLTEXANIMSECTION textureAnims[numTexAnims];
};

struct MDLTEXANIMSECTION
{
  uint32_t size;
  
  KTAT transkeys;
  KTAR rotkeys;
  KTAS scalekeys;
};

KTAT

Texture animation translation track

struct KTAT
{
  char tag[4]; // KTAT
  MDLKEYTRACK<C3Vector> transkeys;
};

KTAR

Texture animation rotation track

struct KTAR
{
  char tag[4]; // KTAR
  MDLKEYTRACK<C4QuaternionCompressed> rotkeys;
};

KTAS

Texture animation scale track

struct KTAS
{
  char tag[4]; // KTAS
  MDLKEYTRACK<C3Vector> scalekeys;
};

GEOS

Geosets.

GEOS (≤ v1400)

struct GEOS
{
  uint32_t numGeosets;                  // limited to 0xFF
  MDLGEOSETSECTION geosets[numGeosets];
};

struct MDLGEOSETSECTION
{
  uint32_t size;
  
  VRTX vertices;
  NRMS normals;
  UAVS texCoords;
  MDLPRIMITIVES primitives;
  GNDX vertGroupIndices;
  MTGC groupMatrixCounts;
  MATS matrices;
  BIDX boneIndices;
  BWGT boneWeights;
  
  uint32_t materialId;                  // MTLS index
  uint32_t selectionGroup;              // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group
                                        // see the related M2 skin section for more information
  uint32_t flags;                       // &1: unselectable
  CMdlBounds bounds;
  uint32_t numSeqBounds;
  CMdlBounds seqBounds[numSeqBounds];
};

struct MDLPRIMITIVES
{
  PTYP types;
  PCNT counts;
  PVTK vertices;
};

enum CHARACTER_GEOSET_SECTIONS
{
  CHARGEOSET_HAIR = 0x0,
  CHARGEOSET_BEARD = 0x1,
  CHARGEOSET_SIDEBURN = 0x2,
  CHARGEOSET_MOUSTACHE = 0x3,
  CHARGEOSET_GLOVE = 0x4,
  CHARGEOSET_BOOT = 0x5,
  CHARGEOSET_OBSOLETEDONTUSEME = 0x6,
  CHARGEOSET_EAR = 0x7,
  CHARGEOSET_SLEEVES = 0x8,
  CHARGEOSET_PANTS = 0x9,
  CHARGEOSET_DOUBLET = 0xA,
  CHARGEOSET_PANTDOUBLET = 0xB,
  CHARGEOSET_TABARD = 0xC,
  CHARGEOSET_ROBE = 0xD,
  CHARGEOSET_LOINCLOTH = 0xE,
  NUM_CHARGEOSETS = 0xF,
  CHARGEOSET_NONE = 0xFFFFFFFF,
};

GEOS (v1500)

This section only applies to version 1500.
struct GEOS
{
  uint32_t numGeosets;                  // limited to 0xFF
  MDLGEOSETSECTION geosets[numGeosets];
  MDLBATCH batches[numGeosets];
};

struct MDLGEOSETSECTION
{
   uint32_t materialId;
   C3Vector boundsCentre;
   float boundsRadius;
   uint32_t selectionGroup;
   uint32_t geosetIndex;
   uint32_t flags;                  // &1: unselectable, &2: ? (unused), &4 ?, &8: ?, &0x10: project2D, &0x20: ?
   
   char vertexTag[4];               // PVTX
   uint32_t vertexCount;
   char primTypeTag[4];             // PTYP
   uint32_t primitiveTypesCount;
   char primVertexTag[4];           // PVTX
   uint32_t primitiveVerticesCount;

   uint32_t unknown1;               // always 0 filled
   uint32_t unknown2;               // always 0 filled
}

struct MDLBATCH
{
   const MDLGEOSETSECTION geoset = GEOS.geosets[index];  // GEOS geoset of matching index
   
   MDLVERTEX vertices[geoset.vertexCount];
   uint32_t primitiveTypes[geoset.primitiveTypesCount];  // always 0x3 (Triangle)
   uint32_t unknown;                                     // always 0
   
   uint32_t numPrimVertices;                             // matches geoset.primitiveVerticesCount
   uint32_t maxVertex;                                   // the largest vertex in the list   
   uint16_t primitiveVertices[numPrimVertices];   
   
 #if numPrimVertices % 8 != 0
   uint16_t padding[x];     // alignment padding, calculated as x = (8 - numPrimVertices % 8)
 #endif
}

struct MDLVERTEX            // same structure as M2Vertex
{
   C3Vector position;
   uint8_t boneWeights[4];
   uint8_t boneIndices[4];
   C3Vector normal;
   C2Vector texCoords[2];  // second is always (0,0) in all beta files
}

VRTX

Vertices. Also used by CLID.

struct VRTX
{
  char tag[4]; // VRTX
  uint32_t count; // limited to 0xFFFF
  C3Vector vertices[count]; 
};

NRMS

Normals. Also used by CLID.

struct NRMS
{
  char tag[4]; // NRMS
  uint32_t count;
  C3Vector normals[count];
};

UVAS

Texture coordinates. The client uses UVAS.count * VRTX.count to calculate how many C2Vectors to read

struct UVAS
{
  char tag[4]; // UVAS
  uint32_t count;
  C2Vector texCoords[count * vertices.count];
};

PTYP

Primitive types. This is always 0x4 (Triangle) although the client appears to support all FACETYPEs

struct PTYP
{
  char tag[4]; // PTYP
  uint32_t count;
  FACETYPE primitiveTypes[count];
};

enum FACETYPE : uint8_t
{
  FACETYPE_POINTS = 0x0,
  FACETYPE_LINES = 0x1,
  FACETYPE_LINE_LOOP = 0x2,
  FACETYPE_LINE_STRIP = 0x3,
  FACETYPE_TRIANGLES = 0x4,
  FACETYPE_TRIANGLE_STRIP = 0x5,
  FACETYPE_TRIANGLE_FAN = 0x6,
  FACETYPE_QUADS = 0x7,
  FACETYPE_QUAD_STRIP = 0x8,
  FACETYPE_POLYGON = 0x9
};

PCNT

Primitive counts. The number of uint16_ts used by PVTX in each group

struct PCNT
{
  char tag[4]; // PCNT
  uint32_t count;
  uint32_t primitiveCounts[count];
};

PVTX

Primitive vertices

struct PVTX
{
  char tag[4]; // PVTX
  uint32_t count;
  uint16_t primitiveVertices[count];
};

GNDX

Vertex group indices

struct GNDX
{
  char tag[4]; // GNDX
  uint32_t count;
  uint8_t vertGroupIndices[count];
};

MTGC

Group matrix counts

struct MTGC
{
  char tag[4]; // MTGC
  uint32_t count;
  uint32_t groupMatrixCounts[count];
};

MATS

Matrices

struct MATS
{
  char tag[4]; // MATS
  uint32_t count;
  uint32_t matrices[count];
};

BIDX

Bone indices

struct BIDX
{
  char tag[4]; // BIDX
  uint32_t count;
  uint32_t boneIndices[count];
};

BWGT

Bone weights

struct BWGT
{
  char tag[4]; // BWGT
  uint32_t count;
  uint32_t boneWeights[count];
};

GEOA

Geoset animations

struct GEOA
{
  uint32_t numGeoAnims;
  MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];
};
 
struct MDLGEOSETANIMSECTION
{
  uint32_t size;
  uint32_t geosetId;        // GEOS index or 0xFFFFFFFF if none
  float staticAlpha;        // 0 is transparent, 1 is opaque
  C3Color staticColor;
  uint32_t flags;           // &1: color
  
  KGAO alphaKeys;
  KGAC colorKeys;
};

KGAO

Animated geoset alpha track

struct KGAO
{
  char tag[4]; // KGAO
  MDLKEYTRACK<float> alphaKeys;
};

KGAC

Animated geoset color track

struct KGAC
{
  char tag[4]; // KGAC
  MDLKEYTRACK<C3Color> colorKeys;
};
       

BONE

Bones

struct BONE
{
  uint32_t numBones;
  MDLBONESECTION bones[numBones];
};

struct MDLBONESECTION : MDLGENOBJECT
{
  MDLGENOBJECT object;
  
  uint32_t geosetId;       // GEOS index or 0xFFFFFFFF if none
  uint32_t geosetAnimId;   // GEOA index or 0xFFFFFFFF if none
};

LITE

Lights.

struct LITE
{
  uint32_t numLights;
  MDLLIGHTSECTION lights[numLights];
};

struct MDLLIGHTSECTION : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  LIGHT_TYPE type;
  float staticAttenStart;
  float staticAttenEnd;
  C3Color staticColor;
  float staticIntensity;  
  C3Color staticAmbColor;
  float staticAmbIntensity;
  
  KLAS attenstartkeys;
  KLAE attenendkeys;
  KLAC colorkeys;
  KLAI intensitykeys;
  KLBC ambcolorkeys;
  KLBI ambintensitykeys;
  KVIS visibilityKeys;
};

enum LIGHT_TYPE : uint32_t
{
  LIGHTTYPE_OMNI = 0x0,
  LIGHTTYPE_DIRECT = 0x1,
  LIGHTTYPE_AMBIENT = 0x2,
  NUM_MDL_LIGHT_TYPES = 0x3,
};

KLAS

Light attenuation start track

struct KLAS
{
  char tag[4]; // KLAS
  MDLKEYTRACK<float> attenstartkeys;
};

KLAE

Light attenuation end track

struct KLAE
{
  char tag[4]; // KLAE
  MDLKEYTRACK<float> attenendkeys;
};

KLAC

Light color track

struct KLAC
{
  char tag[4]; // KLAC
  MDLKEYTRACK<C3Color> colorkeys;
};

KLAI

Light intensity track

struct KLAI
{
  char tag[4]; // KLAI
  MDLKEYTRACK<float> intensitykeys;
};

KLBC

Light ambience color track

struct KLBC
{
  char tag[4]; // KLBC
  MDLKEYTRACK<C3Color> ambcolorkeys
};

KLBI

Light ambient intensity track

struct KLBI
{
  char tag[4]; // KLBI
  MDLKEYTRACK<float> ambintensitykeys;
};

KVIS

Visiblity track. Note: Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0

struct KVIS
{
  char tag[4]; // KVIS
  MDLKEYTRACK<float> visibilityKeys;
};

HELP

Helpers.

struct HELP
{
  uint32_t count;
  MDLGENOBJECT helpers[count];
};

ATCH

Attachment Points.

struct ATCH
{
  uint32_t numAttachments;
  uint32_t unused;                 // has values but is ignored by the client
  
  MDLATTACHMENTSECTION attachments[numAttachments];
};

struct MDLATTACHMENTSECTION : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;             // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum
  
  GEOCOMPONENTLINKS attachmentId;
  uint8_t padding;
  char path[0x104];                // 0 filled in all alpha files
  
  KVIS visibilityKeys;
};

enum GEOCOMPONENTLINKS : uint32_t
{
  ATTACH_SHIELD = 0x0,
  ATTACH_HANDR = 0x1,
  ATTACH_HANDL = 0x2,
  ATTACH_ELBOWR = 0x3,
  ATTACH_ELBOWL = 0x4,
  ATTACH_SHOULDERR = 0x5,
  ATTACH_SHOULDERL = 0x6,
  ATTACH_KNEER = 0x7,
  ATTACH_KNEEL = 0x8,
  ATTACH_HIPR = 0x9,
  ATTACH_HIPL = 0xA,
  ATTACH_HELM = 0xB,
  ATTACH_BACK = 0xC,
  ATTACH_SHOULDERFLAPR = 0xD,
  ATTACH_SHOULDERFLAPL = 0xE,
  ATTACH_TORSOBLOODFRONT = 0xF,
  ATTACH_TORSOBLOODBACK = 0x10,
  ATTACH_BREATH = 0x11,
  ATTACH_PLAYERNAME = 0x12,
  ATTACH_UNITEFFECT_BASE = 0x13,
  ATTACH_UNITEFFECT_HEAD = 0x14,
  ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,
  ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,
  ATTACH_UNITEFFECT_SPECIAL1 = 0x17,
  ATTACH_UNITEFFECT_SPECIAL2 = 0x18,
  ATTACH_UNITEFFECT_SPECIAL3 = 0x19,
  ATTACH_SHEATH_MAINHAND = 0x1A,
  ATTACH_SHEATH_OFFHAND = 0x1B,
  ATTACH_SHEATH_SHIELD = 0x1C,
  ATTACH_PLAYERNAMEMOUNTED = 0x1D,
  ATTACH_LARGEWEAPONLEFT = 0x1E,
  ATTACH_LARGEWEAPONRIGHT = 0x1F,
  ATTACH_HIPWEAPONLEFT = 0x20,
  ATTACH_HIPWEAPONRIGHT = 0x21,
  ATTACH_TORSOSPELL = 0x22,
  ATTACH_HANDARROW = 0x23,
  NUM_ATTACH_SLOTS = 0x24,
  ATTACH_NONE = 0xFFFFFFFF,
};

PIVT

Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with MDLGENOBJECTs by matching indices.

struct PIVT
{
  C3Vector pivotPoints[chunk.size / 0xC];	 
};

PREM

Particle emitters. Note: This is deprecated use PRE2 instead.

struct PREM
{ 
  uint32_t numEmitters;
  MDLPARTICLEEMITTER emitters[numEmitters];
};

struct MDLPARTICLEEMITTER : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  float staticEmissionRate;  
  float staticGravity;  
  float staticLongitude;
  float staticLatitude;
  MDLPARTICLE particle;
  
  KPEE emissionRate;
  KPEG gravity;
  KPLN longitude;
  KPLT latitude;
  KVIS visibilityKeys;
};

struct MDLPARTICLE
{
  char path[0x104];       // model path
  float staticLife;
  float staticSpeed;
  
  KPEL life;
  KPES speed;
};

KPEE

Particle emitter emission rate track

struct KPEE
{
  char tag[4]; // KPEE
  MDLKEYTRACK<float> emissionRate;
};

KPEG

Particle emitter particle gravity track

struct KPEG
{
  char tag[4]; // KPEG
  MDLKEYTRACK<float> gravity;
};

KPLT

Particle emitter particle latitude track

struct KPLT
{
  char tag[4]; // KPLT
  MDLKEYTRACK<float> latitude;
};

KPEL

Particle emitter particle life track

struct KPEL
{
  char tag[4]; // KPEL
  MDLKEYTRACK<float> life;
};

KPES

Particle emitter particle speed track

struct KPES
{
  char tag[4]; // KPES
  MDLKEYTRACK<float> speed;
};

CAMS

Cameras.

struct CAMS
{
  uint32_t numCameras;
  MDLCAMERASECTION cameras[numCameras];   
};

struct MDLCAMERASECTION
{
  uint32_t size;
  char name[0x50];        // common names are CameraPortrait, Portrait and Paperdoll
  C3Vector pivot;
  float fieldOfView;      // default is 0.9500215
  float farClip;          // default is 27.7777786
  float nearClip;         // default is 0.222222224
  C3Vector targetPivot;
  
  KCTR transkeys;
  KCRL rollkeys;
  KVIS visibilityKeys;
  KTTR targettranskeys;
};

KCTR

Camera translation track

struct KCTR
{
  char tag[4]; // KCTR
  MDLKEYTRACK<C3Vector> transkeys;
};

KCRL

Camera roll track

struct KCRL
{
  char tag[4]; // KCRL
  MDLKEYTRACK<float> rollkeys;
};

KTTR

Camera target translation track

struct KTTR
{
  char tag[4]; // KTTR
  MDLKEYTRACK<C3Vector> targettranskeys;
};

EVTS

Events. For a complete list see the M2 events section.

struct EVTS
{
  uint32_t numEventObjs;
  MDLEVENTSECTION events[numEventObjs];
};

struct MDLEVENTSECTION : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  KEVT eventKeys;
};

KEVT

Event time track

struct KEVT
{
  char tag[4]; // KEVT
  MDLKEYTRACK<MDLEVENTKEY> eventKeys;
};

PRE2

Particle Emitter 2, the successor of the PREM chunk.

struct PRE2
{
  uint32_t numEmitters;
  MDLPARTICLEEMITTER2 emitters[numEmitters];
};

struct MDLPARTICLEEMITTER2 : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  uint32_t emitterSize;
  PARTICLE_EMITTER_TYPE emitterType;
  float staticSpeed;          // particleVelocity
  float staticVariation;      // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier
  float staticLatitude;
  float staticLongitude;
  float staticGravity;        // particleAcceleration, only applied to the z axis
  float staticZsource;        // deducted from the particle starting z position. must be ≥ 0.0
  float staticLife;           // base particle lifespan in seconds
  float staticEmissionRate;   // base amount of particles per second. client treats negatives as 0.0
  float staticLength;         // height, for PET_SPLINE endAngle (multiplied by emissionRate), for PET_SPHERE outerRadius
  float staticWidth;          // width, for PET_SPLINE startAngle, for PET_SPHERE innerRadius
  uint32_t rows;
  uint32_t cols;
  PARTICLE_TYPE type; 
  float tailLength;
  float middleTime;
  C3Color startColor;
  C3Color middleColor;
  C3Color endColor;
  uint8_t startAlpha;
  uint8_t middleAlpha;
  uint8_t endAlpha;   
  float startScale;
  float middleScale;
  float endScale;
  uint32_t lifespanUVAnimStart;
  uint32_t lifespanUVAnimEnd;
  uint32_t lifespanUVAnimRepeat;
  uint32_t decayUVAnimStart;
  uint32_t decayUVAnimEnd;
  uint32_t decayUVAnimRepeat;
  uint32_t tailUVAnimStart;
  uint32_t tailUVAnimEnd;
  uint32_t tailUVAnimRepeat;
  uint32_t tailDecayUVAnimStart;
  uint32_t tailDecayUVAnimEnd;
  uint32_t tailDecayUVAnimRepeat;
  PARTICLE_BLEND_MODE blendMode;
  uint32_t textureId;         // TEXS index or 0xFFFFFFFF if none
  int32_t priorityPlane;      // priority is sorted lowest to highest
  uint32_t replaceableId;     // only seen in Wisp.mdx
  char geometryMdl[0x104];    // particle model
  char recursionMdl[0x104];  
  float twinkleFPS;           // default is 10.0
  float twinkleOnOff;         // boolean, twinkle applies additional scaling to make a shrink and grow effect
  float twinkleScaleMin;      // twinkle is not applied if twinkleScaleMax - twinkleScaleMin == 0.0
  float twinkleScaleMax;
  float ivelScale;            // instant velocity scale, multiplier for each particle's intial velocity
  float tumblexMin;           // tumble adds a randomised rotation to each particle
  float tumblexMax;
  float tumbleyMin;
  float tumbleyMax;
  float tumblezMin;
  float tumblezMax;
  float drag;                 // decreases particle velocity over time
  float spin;
  C3Vector windVector;       // simulates being blown
  float windTime;             // how long windVector is to be applied
  float followSpeed1;
  float followScale1;
  float followSpeed2;
  float followScale2;
  uint32_t numSplines;
  C3Vector spline[numSplines];
  uint32_t squirts;           // boolean
  
  KVIS visibilityKeys;
  KP2S speed;
  KP2R variation;
  KP2L latitude;
  KPLN longitude;
  KP2G gravity;
  KLIF life;
  KP2E emissionRate;
  KP2W width;
  KP2N length;
  KP2Z zsource;
};

enum PARTICLE_BLEND_MODE : uint32_t
{
  PBM_BLEND = 0x0,
  PBM_ADD = 0x1,
  PBM_MODULATE = 0x2,
  PBM_MODULATE_2X = 0x3,
  PBM_ALPHA_KEY = 0x4,
  NUM_PARTICLE_BLEND_MODES = 0x5,
};

enum PARTICLE_TYPE : uint32_t
{
  PT_HEAD = 0x0,
  PT_TAIL = 0x1,
  PT_BOTH = 0x2,
  NUM_PARTICLE_TYPES = 0x3,
};

enum PARTICLE_EMITTER_TYPE : uint32_t
{
  PET_BASE = 0x0,
  PET_PLANE = 0x1,
  PET_SPHERE = 0x2,
  PET_SPLINE = 0x3,
  NUM_PARTICLE_EMITTER_TYPES = 0x4,
};

KP2S

Particle emitter 2 speed track

struct KP2S
{
  char tag[4]; // KP2S
  MDLKEYTRACK<float> speed;
};

KP2R

Particle emitter 2 variation track

struct KP2R
{
  char tag[4]; // KP2R
  MDLKEYTRACK<float> variation;
};

KP2L

Particle emitter 2 latitude track

struct KP2L
{
  char tag[4]; // KP2L
  MDLKEYTRACK<float> latitude;
};

KPLN

Particle emitter 2 longitude track

struct KPLN
{
  char tag[4]; // KPLN
  MDLKEYTRACK<float> longitude;
};

KP2G

Particle emitter 2 gravity track

struct KP2G
{
  char tag[4]; // KP2G
  MDLKEYTRACK<float> gravity;
};

KLIF

Particle emitter 2 life track

struct KLIF
{
  char tag[4]; // KLIF
  MDLKEYTRACK<float> life;
};

KP2E

Particle emitter 2 emission rate track

struct KP2E
{
  char tag[4]; // KP2E
  MDLKEYTRACK<float> emissionRate; 
};

KP2W

Particle emitter 2 width track

struct KP2W
{
  char tag[4]; // KP2W
  MDLKEYTRACK<float> width;
};

KP2N

Particle emitter 2 length track

struct KP2N
{
  char tag[4]; // KP2N
  MDLKEYTRACK<float> length;
};

KP2Z

Particle emitter 2 zsource track

struct KP2Z
{
  char tag[4]; // KP2Z
  MDLKEYTRACK<float> zsource;
};

HTST

Hit test shapes.

struct HTST
{
  uint32_t numHitTestShapes;
  MDLHITTESTSHAPE hittestshapes[numHitTestShapes];
};

struct MDLHITTESTSHAPE : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  GEOM_SHAPE type;
  
 #if type == SHAPE_BOX:
  MDLBOX box;
 #elseif type == SHAPE_CYLINDER:
  MDLCYLINDER cylinder;
 #elseif type == SHAPE_SPHERE:
  MDLSPHERE sphere;
 #elseif type == SHAPE_PLANE:
  MDLPLANE plane;
 #endif
};

enum GEOM_SHAPE : uint8_t
{
  SHAPE_BOX = 0x0,
  SHAPE_CYLINDER = 0x1,
  SHAPE_SPHERE = 0x2,
  SHAPE_PLANE = 0x3,
  NUM_SHAPES = 0x4,
};

struct MDLBOX
{
  C3Vector minimum;
  C3Vector maximum;
};

struct MDLCYLINDER
{
  C3Vector base;
  float height;
  float radius;
};

struct MDLSPHERE
{
  C3Vector center;
  float radius;
};

struct MDLPLANE
{
  float length;
  float width;
};

RIBB

Ribbon emitter.

struct RIBB
{
  uint32_t numEmitters;
  MDLRIBBONEMITTER emitters[numEmitters];
};

struct MDLRIBBONEMITTER : MDLGENOBJECT
{
  uint32_t size;
  MDLGENOBJECT object;
  
  uint32_t emitterSize;
  float staticHeightAbove;        // must be ≥ 0.0
  float staticHeightBelow;        // must be ≥ 0.0
  float staticAlpha;              // 0 is transparent, 1 is opaque
  C3Color staticColor;
  float edgeLifetime;             // in seconds. must be > 0.0, client forces a minimum of 0.25s
  uint32_t staticTextureSlot;
  uint32_t edgesPerSecond;        // must be ≥ 1.0
  uint32_t textureRows;
  uint32_t textureCols;  
  uint32_t materialId;            // MTLS index
  float gravity;  

  KRHA heightAbove;
  KRHB heightBelow;
  KRAL alphaKeys;
  KRCO colorKeys;
  KRTX textureSlot;               // unused by alpha files
  KVIS visibilityKeys;
};

KRHA

Ribbon emitter height above track

struct KRHA
{
  char tag[4]; // KRHA
  MDLKEYTRACK<float> heightAbove;
};

KRHB

Ribbon emitter height below track

struct KRHB
{
  char tag[4]; // KRHB
  MDLKEYTRACK<float> heightBelow;
};

KRAL

Ribbon emitter alpha track

struct KRAL
{
  char tag[4]; // KRAL
  MDLKEYTRACK<float> alphaKeys;
};

KRCO

Ribbon emitter color track

struct KRCO
{
  char tag[4]; // KRCO
  MDLKEYTRACK<C3Color> colorKeys;
};

KRTX

Ribbon emitter texture slot track

struct KRTX
{
  char tag[4]; // KRTX
  MDLSIMPLEKEYTRACK<MDLINTKEY> textureSlot;
};

CLID

Collision.

struct MDLCOLLISION
{
  VRTX vertices;
  TRI triIndices; 
  NRMS facetNormals;
};

TRI

Triangles

struct TRI
{
  char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional
  uint32_t count;
  uint16_t triIndices[count];
};