M2: Difference between revisions

From wowdev
Jump to navigation Jump to search
mNo edit summary
(Remove hardcoded table styling)
 
(758 intermediate revisions by 46 users not shown)
Line 1: Line 1:
==  Header ==
<div style="float: right; margin-left: 5px;">__TOC__</div>
[[M2]] files (also called [[MDX]]) contain model objects. Each [[M2]] file describes the vertices, faces, materials, texture names, animations and properties of one model. [[M2]] files don't have a chunked format like most other WoW formats (except in Legion). Since it is chunked in Legion, all offsets are relative to beginning of the MD21 chunk's data rather than the beginning of the file.


Models are used for doodads (decoration objects), players, monsters and really everything in the game except for [[ADT|Terrain]] and [[WMO]]s.
M2 files do not store all the data for the model in them. Additional model information is stored in these files:  [[M2#.anim_files|.anim]], [[M2/.skin|.skin]], [[PHYS|.phys]], [[BONE|.bone]], [[M2/.skel|.skel]] which may vary depending on the client version.
Details on how to request and read them are described in the page below.
==Header==
The header has mostly the layout of number-offset pairs, containing the number of a particular record in the file, and the offset. These appear at fixed places in the header. Record sizes are not specified in the file.
The header has mostly the layout of number-offset pairs, containing the number of a particular record in the file, and the offset. These appear at fixed places in the header. Record sizes are not specified in the file.


  '''Offset Type Name Description'''
  struct
  ''0x000'' char[4] Magic "MD20"
{
  ''0x004'' uint32 Version 0x80100000 (first digit of the build the format was last updated)
// note: Offsets are for {{Template:Sandbox/VersionRange|min_expansionlevel=3}}!
''0x008'' uint32 lName Length of the model's name
  /*0x000*/  uint32_t magic;                                      // "MD20". [[M2#Legion|Legion]] uses a chunked file format starting with MD21.
  ''0x00C'' uint32 ofsName Offset to the name
  /*0x004*/  uint32_t [[#Versions|version]];
  ''0x010'' uint32 Type Type of the model. Connected to field 4 of [[CreatureModelData.dbc]]?
/*0x008*/  {{Template:Type/M2Array|char}} name;                                  // should be globally unique, used to reload by name in internal clients, empty string in files updated in or after 9.2.0.41462+
  ''0x014'' uint32 nGlobalSequences
  ''0x018'' uint32 [[M2/WotLK#Global_sequences|ofsGlobalSequences]] A list of timestamps.
/*0x010*/  struct
  ''0x01C'' uint32 nAnimations
            {
  ''0x020'' uint32 [[M2/WotLK#Animation_sequences|ofsAnimations]] Information about the animations in the model.
              uint32_t flag_tilt_x : 1;
  ''0x024'' uint32 nAnimationLookup
              uint32_t flag_tilt_y : 1;
  ''0x028'' uint32 [[M2/WotLK#Animation_Lookup|ofsAnimationLookup]] Mapping of [[M2/WotLK#Animation_sequences|global IDs]] to the entries in the [[M2/WotLK#Animation_sequences|Animation sequences]] block.
              uint32_t : 1;
  ''0x02C'' uint32 nBones
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}}                                              // TODO: verify version
  ''0x030'' uint32 [[M2/WotLK#Bones|ofsBones]] Information about the bones in this model.
              uint32_t {{Verified|flag_use_texture_combiner_combos}} : 1;      // add textureCombinerCombos array to end of data (alt. name: Second_Texture_Material_Override_Combos)
  ''0x034'' uint32 nKeyBoneLookup
              uint32_t : 1;
  ''0x038'' uint32 [[M2/WotLK#Key-Bone_Lookup|ofsKeyBoneLookup]] Lookup table for key skeletal bones.
#if {{Template:Sandbox/VersionRange|min_expansionlevel=5}}
  ''0x03C'' uint32 nVertices
              uint32_t flag_load_phys_data : 1;
  ''0x040'' uint32 [[M2/WotLK#Vertices|ofsVertices]] Vertices of the model.
              uint32_t : 1;
  '''''0x044''''' '''uint32 nViews or Unknown Once this was nViews and it still is 4. Whatever happened to ofsViews, we will get it back!'''
#if {{Template:Sandbox/VersionRange|min_expansionlevel=6}}
  ''0x048'' uint32 nColors
              uint32_t flag_unk_0x80 : 1;                        // with this flag unset, demon hunter tattoos stop glowing
  ''0x04C'' uint32 [[M2/WotLK#Colors|ofsColors]] Color definitions.
                                                                  // since {{Template:Unverified|{{Template:Sandbox/PrettyVersion|expansionlevel=4|build=4.0.1.12911}}}} {{Template:Unverified|every}} model now has this flag
  ''0x050'' uint32 nTextures
              uint32_t flag_camera_related : 1;                  // TODO: verify version
  ''0x054'' uint32 [[M2/WotLK#Texture_definitions|ofsTextures]] Textures of this model. Now .skins?
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}                                                      // TODO: verify version, these are just added based on where I first saw them -- schlumpf.
  ''0x058'' uint32 nTransparency
              uint32_t flag_new_particle_record : 1;              // In CATA: new version of ParticleEmitters. By default, length of M2ParticleOld is 476.
  ''0x05C'' uint32 [[M2/WotLK#Transparency|ofsTransparency]] Transparency of textures.
                                                                  // But if 0x200 is set or if version is bigger than 271, length of M2ParticleOld is 492.
  ''0x060'' uint32 nUnknown
              uint32_t flag_unk_0x400 : 1;
''0x064'' uint32 [[M2/WotLK#Colors|ofsUnknown]] This was never in use. Both are 0 all the time.
              uint32_t flag_texture_transforms_use_bone_sequences : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=6}} 0x800 -- When set, texture transforms are animated using the sequence being played on the bone found by index in tex_unit_lookup_table[textureTransformIndex], instead of using the sequence being played on the model's first bone. Example model: 6DU_HellfireRaid_FelSiege03_Creature
  ''0x068'' uint32 nTexReplace
              uint32_t flag_unk_0x1000 : 1;
  ''0x06C'' uint32 [[M2/WotLK#Replacable_texture_lookup|ofsTexReplace]] Replaceable Textures.
              uint32_t ChunkedAnimFiles_0x2000 : 1;              // seen in various legion models
  ''0x070'' uint32 nRenderFlags
              uint32_t flag_unk_0x4000 : 1;
  ''0x074'' uint32 [[M2/WotLK#Render_flags|ofsRenderFlags]] Blending modes / render flags.
              uint32_t flag_unk_0x8000 : 1;                      // seen in UI_MainMenu_Legion
  ''0x078'' uint32 nBoneLookupTable
              uint32_t flag_unk_0x10000 : 1;
  ''0x07C'' uint32 [[M2/WotLK#Bone_Lookup_Table|ofsBoneLookupTable]] A bone lookup table.
              uint32_t flag_unk_0x20000 : 1;
  ''0x080'' uint32 nTexLookup
              uint32_t flag_unk_0x40000 : 1;
  ''0x084'' uint32 [[M2/WotLK#Texture_lookup_table|ofsTexLookup]] The same for textures.
              uint32_t flag_unk_0x80000 : 1;
  ''0x088'' uint32 nTexUnits
              uint32_t flag_unk_0x100000 : 1;
  ''0x08C'' uint32 [[M2/WotLK#Texture_unit_lookup_table|ofsTexUnits]] And texture units. Somewhere they have to be too.
              uint32_t flag_unk_0x200000 : 1;                    // apparently: use 24500 upgraded model format: chunked .anim files, change in the exporter reordering sequence+bone blocks before name
  ''0x090'' uint32 nTransLookup
  #endif
  ''0x094'' uint32 [[M2/WotLK#Transparency_lookup_table|ofsTransLookup]] Everything needs its lookup. Here are the transparencies.
#endif
  ''0x098'' uint32 nTexAnimLookup
#endif
  ''0x09C'' uint32 [[M2/WotLK#Texture_animation_lookup_table|ofsTexAnimLookup]] Wait. Do we have animated Textures? Wasn't ofsTexAnims deleted? oO
  #endif
  ''0x0A0'' float theFloats[14] Noone knows. Meeh, they are here.  
            } global_flags;
  ''0x0D8'' uint32 nBoundingTriangles
       
  ''0x0DC'' uint32 [[M2/WotLK#Bounding_volumes|ofsBoundingTriangles]] Our bounding volumes. Similar structure like in the old ofsViews.
  /*0x014*/ {{Template:Type/M2Array|M2Loop|section=Global_sequences}} global_loops;                        // Timestamps used in [[#Global_Sequences_2|global looping animations]].
  ''0x0E0'' uint32 nBoundingVertices
  /*0x01C*/ {{Template:Type/M2Array|M2Sequence|section=Animation_sequences}} sequences;                      // Information about the animations in the model.
  ''0x0E4'' uint32 [[M2/WotLK#Bounding_volumes|ofsBoundingVertices]]
  /*0x024*/ {{Template:Type/M2Array|uint16_t|section=Animation_Lookup}} sequenceIdxHashById;              // Mapping of [[#Animation_sequences|sequence IDs]] to the entries in the [[#Animation_sequences|Animation sequences]] block.
  ''0x0E8'' uint32 nBoundingNormals
  #if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}
  ''0x0EC'' uint32 [[M2/WotLK#Bounding_volumes|ofsBoundingNormals]]
            {{Template:Type/M2Array|M2SequenceFallback|section=Playable_Animation_Lookup}} playable_animation_lookup;
  ''0x0F0'' uint32 nAttachments
#endif
  ''0x0F4'' uint32 [[M2/WotLK#Block_1|ofsAttachments]] Attachments are for weapons etc.
/*0x02C*/ {{Template:Type/M2Array|M2CompBone|section=Bones}} bones;                          // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones ({{Template:Sandbox/PrettyVersion|expansionlevel=3}})
  ''0x0F8'' uint32 nAttachLookup
                                                                                      => World\Expansion01\Doodads\Auchindoun\Passivedoodads\Bridge_FX\Auchindoun_Bridge_Spirits_Flying.m2 has 305 bones ({{Template:Sandbox/PrettyVersion|expansionlevel=2}})
  ''0x0FC'' uint32 [[M2/WotLK#Attachment_Lookup|ofsAttachLookup]] Of course with a lookup.
  /*0x034*/ {{Template:Type/M2Array|uint16_t|section=Key-Bone_Lookup}} boneIndicesById;                  //Lookup table for key skeletal bones. (alt. name: key_bone_lookup)
  ''0x100'' uint32 nAttachments_2
  /*0x03C*/  {{Template:Type/M2Array|M2Vertex|section=Vertices}} vertices;
  ''0x104'' uint32 [[M2/WotLK#Block_2|ofsAttachments_2]] And some second block.
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}
  ''0x108'' uint32 nLights
            {{Template:Type/M2Array|M2SkinProfile|section=Views_.28LOD.29}} skin_profiles;
  ''0x10C'' uint32 [[M2/WotLK#Lights|ofsLights]] Lights are mainly used in loginscreens but in wands and some doodads too.
#else
  ''0x110'' uint32 nCameras
  /*0x044*/  uint32_t num_skin_profiles;                          // [[#Views_.28LOD.29|Views (LOD)]] are now in [[M2/.skin|.skins]].
  ''0x114'' uint32 [[M2/WotLK#Cameras|ofsCameras]] The cameras are present in most models for having a model in the Character-Tab.  
  #endif
  ''0x118'' uint32 nCameraLookup
         
  ''0x11C'' uint32 [[M2/WotLK#Camera_lookup_table|ofsCameraLookup]] And lookup-time again.
  /*0x048*/ {{Template:Type/M2Array|M2Color|section=Colors}} colors;                            // Color and alpha animations definitions.
  ''0x120'' uint32 nRibbonEmitters
  /*0x050*/ {{Template:Type/M2Array|M2Texture|section=Textures}} textures;
  ''0x124'' uint32 [[M2/WotLK#Ribbon_emitters|ofsRibbonEmitters]] Things swirling around. See the CoT-entrance for light-trails.
  /*0x058*/ {{Template:Type/M2Array|M2TextureWeight|section=Transparency}} texture_weights;            // Transparency of textures.
  ''0x128'' uint32 nParticleEmitters
  #if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}
  ''0x12C'' uint32 [[M2/WotLK#Particle_emitters|ofsParticleEmitters]] Spells and weapons, doodads and loginscreens use them. Blood dripping of a blade? Particles.
            {{Template:Type/M2Array|M2TextureFlipbook}} texture_flipbooks;                        // never seen in file, 4 uint32_t fields. (M2Track<ushort>)
#endif
/*0x060*/  {{Template:Type/M2Array|M2TextureTransform|section=Texture_Transforms}} texture_transforms;
  /*0x068*/ {{Template:Type/M2Array|uint16_t|section=Replacable_texture_lookup}} textureIndicesById;                // (alt. name: replacable_texture_lookup)
  /*0x070*/ {{Template:Type/M2Array|M2Material|section=Render_flags}} materials;                      // Blending modes / render flags.
  /*0x078*/ {{Template:Type/M2Array|uint16_t|section=Bone_Lookup_Table}} boneCombos;                        // (alt. name: bone_lookup_table)
  /*0x080*/ {{Template:Type/M2Array|uint16_t|section=Texture_lookup_table}} textureCombos;                    // (alt. name: texture_lookup_table)
  /*0x088*/ {{Template:Type/M2Array|uint16_t|section=Texture_mapping_lookup_table}} {{Verified|textureCoordCombos}};          // (alt. name: tex_unit_lookup_table, texture_mapping_lookup_table)
  /*0x090*/ {{Template:Type/M2Array|uint16_t|section=Transparency_lookup_table}} textureWeightCombos;              // (alt. name: transparency_lookup_table)
  /*0x098*/ {{Template:Type/M2Array|uint16_t|section=Texture_Transforms_lookup_table}} textureTransformCombos;            // (alt. name: texture_transforms_lookup_table)
         
  /*0x0A0*/  {{Template:Type|CAaBox}} bounding_box;                                // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height
/*0x0B8*/  float bounding_sphere_radius;                        // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …)
  /*0x0BC*/  {{Template:Type|CAaBox}} collision_box;
/*0x0D4*/  float collision_sphere_radius;
         
/*0x0D8*/ {{Template:Type/M2Array|uint16_t|section=Triangles}} collisionIndices;                    // (alt. name: collision_triangles)
  /*0x0E0*/ {{Template:Type/M2Array|C3Vector|section=Vertices_2}} collisionPositions;                  // (alt. name: collision_vertices)
  /*0x0E8*/ {{Template:Type/M2Array|C3Vector|section=Normals}} collisionFaceNormals;                // (alt. name: collision_normals)
  /*0x0F0*/ {{Template:Type/M2Array|M2Attachment|section=Attachments}} attachments;                    // position of equipped weapons or effects
  /*0x0F8*/ {{Template:Type/M2Array|uint16_t|section=Attachment_Lookup}} attachmentIndicesById;              // (alt. name: attachment_lookup_table)
  /*0x100*/ {{Template:Type/M2Array|M2Event|section=Events}} events;                              // Used for playing sounds when dying and a lot else.
  /*0x108*/ {{Template:Type/M2Array|M2Light|section=Lights}} lights;                              // Lights are mainly used in loginscreens but in wands and some doodads too.
  /*0x110*/ {{Template:Type/M2Array|M2Camera|section=Cameras}} cameras;                            // The cameras are present in most models for having a model in the character tab.  
  /*0x118*/ {{Template:Type/M2Array|uint16_t|section=Camera_lookup_table}} cameraIndicesById;                  // (alt. name: camera_lookup_table)
  /*0x120*/ {{Template:Type/M2Array|M2Ribbon|section=Ribbon_emitters}} ribbon_emitters;                    // Things swirling around. See the CoT-entrance for light-trails.
  /*0x128*/  {{Template:Type/M2Array|M2Particle|section=Particle_emitters}} particle_emitters;
         
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}}                                                      // TODO: verify version
            if (flag_use_texture_combiner_combos)
            {
  /*0x130*/    {{Template:Type/M2Array|uint16_t|section=Second Texture Material Override Combo}} textureCombinerCombos;          // (alt. name: Second_Texture_Material_Override_Combos) When set, m2 multitexturing will use the second material from here, instead of current index material + 1, for blending with the first texture
            }
#endif
} header;
 
==Types==
struct M2Bounds {
  CAaBox extent;
  float radius;
};
template<typename T>
struct M2Array {
  uint32_t size;
  uint32_t offset; // pointer to T, relative to begin of m2 data block (i.e. MD21 chunk content or begin of file)
};
struct M2TrackBase {
  uint16_t trackType;
  uint16_t loopIndex;
  M2Array<M2SequenceTimes> sequenceTimes;
};
template<typename T>
struct M2PartTrack {
  M2Array<fixed16> times;
  M2Array<T> values;
};
template<typename T>
struct M2SplineKey {
  T value;
  T inTan;
  T outTan;
};
struct M2Range {
  uint32_t minimum;
  uint32_t maximum;
};


--[[User:Schlumpf|schlumpf_]] 03:12, 21 August 2008 (CEST)
==Versions==
--[[User:Xayo|Xayo]]
Files get handled differently depending on this! Ranges are inclusive. Note that these versions are only rough estimates of their range. Prefer looking at the model you're opening rather than relying on the expansion mapping here.


= Skeleton and animation =
{| class="wikitable"
! Version !! Version (Major, Minor) !! Expansion
|-
| 272-274 || 1.16-1.18 || Legion, Battle for Azeroth, Shadowlands
|-
| 272 || 1.16 || Mists of Pandaria, Warlords of Draenor
|-
| 265-272 || 1.9-1.16 || Cataclysm
|-
| 264 || 1.8 || Wrath of the Lich King
|-
| 260-263 || 1.4-1.7 || The Burning Crusade
|-
| 256-257 || 1.0-1.1 || Classic
|-
| 256 || 1.0 || Pre-Release
|}


== Standard animation block ==
The version is most likely a double-byte with major and minor version. This makes version 256 to 1.0, and 274 to 1.18 respectively.
*'''Many values that change with time are specified using blocks like the following. The structure changed in WotLK. Information will come soon.'''


'''Offset Type Name Description'''
==Chunks==
''0x000'' uint16 InterpolationType How the values are interpolated. (0=none, 1=linear, 2=hermite)
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}
''0x002'' uint16 GlobalSequenceID If its made with a global sequence, its in here. Id is it is, -1 if not.
''0x004'' uint32 nA Number of the first informations in the block.
''0x008'' uint32 infA First informations in the block.
''0x00C'' uint32 nB Number of the second informations in the block.
''0x010'' uint32 infB Second informations in the block.


--[[User:Schlumpf|schlumpf_]] 02:01, 21 August 2008 (CEST)
From Legion and up, the file might be [[Chunk|chunked]] instead. If this is the case, the magic will be anything but 'MD20' and the m2 data will be in the 'MD21' chunk. If the first magic is 'MD20', it will be loaded just fine like it did previously. Note that the chunks can be in any order with MD21 often being first.


*'''''Old information'''''
'''NOTE''': Unlike all other chunked formats in WoW, chunk names in M2 are '''NOT''' reversed. Example: AFID == AFID in file.
''The values can have various types depending on the kind of data required, like floats, vectors, quaternions etc.''


''Usually, if animation is used, interpolation ranges will be given for each animation in the global animation list. These ranges refer to keyframes that have given time and data values. The number of timestamps should therefore be equal to the number of data values.''
===MD21===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}
The MD21 chunk contains the not yet chunked data, just like in the old format. The content literally is an pre legion file, starting with the MD20 magic. This also implies that all offsets inside this chunk are relative to the ''chunk'', not the ''file''.
M2Data pre_legion_style_data;


''If a global sequence is used, it means there is an implicit interpolation range across all values, and a time range from 0 to the proper global sequence timestamp.''
===PFID===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}.phys</tt>}}
uint32_t phys_file_id;


''If the interpolation type is 0, in some cases that might mean that no animation is given (like for bones), in other cases it means that a single constant data value should be used (like for colors and effect paramters)''
===SFID===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}${view}.skin</tt> and <tt>${basename}_lod${lodband}.skin</tt>}}
uint32_t skinFileDataIDs[header.nViews];
uint32_t lod_skinFileDataIDs[lodBands /* 2? */];
Some model files, for example 'Creature\NightborneFemaleCitizen\NightborneFemaleCitizen.m2' have 4 skin files and 2 lod files but only 20 bytes are in chunk. In chunk there are 4 skins and 1 lod present.


''Interpolation types:''
Lod skins are selected based on distance to entity/doodad and chosen based on GetCVar("entityLodDist")/X and GetCVar("doodadLodDist")/X where X - distance. Lods are ignored when "M2UseLOD" CVar is set to 0.
'''''Value Description'''''
''0 none / static value''
''1 linear''
''2 (spline?) in addition to values, in/out tangents are given''


''Maybe the interpolation types here are the same as the WC3 MDL files? (bezier, etc)''
===AFID===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}${anim_id}-${sub_anim_id}.anim</tt>}}
struct
{
  uint16_t anim_id;
  uint16_t sub_anim_id;
  uint32_t file_id;        // might be 0 for "none" (so this is probably not sparse, even if it could be)
} anim_file_ids[];


==  Global sequences ==
===BFID===
*'''nGlobalSequences 32-bit unsigned integers starting at ofsGlobalSequences.'''
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}_${i}.bone</tt>}}
A list of timestamps that act as upper limits for global sequence ranges.
  uint32_t boneFileDataIDs[];
 
===TXAC===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=It is unknown what this replaced. {{Unverified|Exact build unknown, not the first one though}}}}
struct {
  char unk[2]; // likely used in CM2SceneRender::SetupTextureTransforms and uploaded to the shader directly. 0 otherwise.
} texture_ac[m2data.header.materials.count + m2data.header.particles.count];
 
 
This chunk doesn't seem to be used directly. Inside CParticleEmitter2 class there are non-null checks that deal with selection of VertexBufferFormat for particles. Apart from that, the usage of these fields is unknown


  '''Offset Type Name Description'''
===EXPT===
  ''0x00'' uint32 Timestamp Entry
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown, not the first one though}}}}
  struct {
  float zSource;
  float colorMult;
  float alphaMult;
  } extended_particle[m2data.header.particles.count];


--[[User:Schlumpf|schlumpf_]] 02:35, 21 August 2008 (CEST)
Probably outdated chunk after introduction of EXP2 chunk. If EXP2 doesnt exist, client tries to reconstruct it with data from EXPT chunk.


== Animation sequences ==
===EXP2===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown}}}}


*'''nAnimations 0x40-byte records starting at ofsAnimations.'''
<source>
List of animations present in the model.
struct M2ExtendedParticle
{
  float zSource;
  float colorMult;
  float alphaMult;
  M2PartTrack<fixed16> alphaCutoff;
};


'''Offset Type Name Description'''
struct M2InitExtendedParticleArray
''0x00'' uint16 AnimationID Animation id in [[AnimationData.dbc]]
{
''0x02'' uint16 SubAnimationID Sub-animation id: Which number in a row of animations this one is.
  M2Array<M2ExtendedParticle> content;
''0x04'' uint32 Length The length (timestamps) of the animation.
} exp2;
''0x08'' float MovingSpeed As 2.x says: moving speed for walk/run animations.
</source>
''0x0C'' uint32 Flags Most likely. All flags I saw: 0b01101101
''0x10'' uint32 Flags 2 Only the first 4 bits are the actual flags. The rest is 1. Values are 0x*FFF. Seen flags: 0,3,6,7
''0x14'' uint32 Unknown 1 These two are connected. Most of the time, they are 0.
''0x16'' uint32 Unknown 2 But if there is data in one, there is data in both of them.
''0x1C'' uint32 PlaybackSpeed Values: 0, 50, 100, 150, 200, 250, 300, 350, 500. '''Was labeled as PlaySpeed in 2.x and earlier.'''
''0x20'' float BoundingBox [6] A Bounding Box made out of 2 vectors.
''0x38'' float Radius The radius.
''0x3C'' int16 NextAnimation Id of the following animation of this AnimationID, points to an Index or is -1 if there is none.
''0x3E'' uint16 Index Id in the list of animations.


--[[User:Schlumpf|schlumpf_]] 00:28, 21 August 2008 (CEST)


==  Animation Lookup ==
The length of this M2Array is the same as length of particle_emitters


*'''nAnimationLookup in  16-bit shorts starting at ofsAnimationLookup.'''
alphaCutoff is for alphaTest per particle. Index into alphaCutoff is particle's current lifetime


Lookup table for Animations in [[AnimationData.dbc]].
colorMult is applied against particle's diffuse color


'''Offset Type Name Description'''
alphaMult is applied against particle's opacity.
''0x00'' uint16 AnimationID Index at [[M2/WotLK#Animation_sequences|ofsAnimations]] which represents the animation in [[AnimationData.dbc]]. -1 if none.


--[[User:Schlumpf|schlumpf_]] 00:45, 21 August 2008 (CEST)
===PABC===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to replace {{Unverified|[[#Animation_Lookup]]}} from old file. {{Unverified|Exact build unknown}}}}
M2Array<uint16t> m_replacementParentSequenceLookups; // only seen in quillboarbrute*.m2. Contains AnimationIds


==  Bones ==
Replaces parentSequencesLookups. But unlike header.sequence_lookups of parent model, this is straight array and not a map. If index with target animation is not found in here, parentSequencesLookups are used instead.


*'''nBones records of 0x5C bytes starting at ofsBones.'''
This chunk called BlacklistAnimData in client.


'''Offset Type Name Description'''
Client doesnt seem to use found index and thus whole array is used only to check if the target animation is present.
''0x00'' uint32 AnimationSeq Index into [[M2/WotLK#Animation_sequences|Animation sequences]] or -1.
''0x04'' uint32 Flags Only known flags: 8 - billborded and 512 - transformed
''0x08'' uint16 ParentBone Parent bone ID or -1 if there is none.
''0x0C'' uint16 GeosetID A geoset for this bone.
''0x10'' DWORD Unknown 4 bytes that have been in since TBC. Noone knows what they are for.
''0x14'' [[M2/WotLK#Standard_animation_block|ABlock]] Translation An animationblock for translation. Should be 3*floats.
''0x28'' [[M2/WotLK#Standard_animation_block|ABlock]] Rotation An animationblock for rotation. Should be 4*shorts, see [[Quaternion_values_and_2.x|Quaternion values and 2.x]].
''0x3C'' [[M2/WotLK#Standard_animation_block|ABlock]] Scaling An animationblock for scaling. Should be 3*floats.
''0x50'' float PivotPoint [3] The pivot point of that bone. Its a vector.


The bone indices in the vertex definitions seem to index into this data.
M2InitBlacklistAnimData: sequenceIds


The billboarding bit is used for various things:
===PADC===
* Light halos around lamps must always face the viewer
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Moves texture weights from old file to a chunk. {{Unverified|Exact build unknown}}}}
* The cannonball stack model (in the Deadmines or Booty Bay), where each cannonball is a crude hemisphere, they always face the viewer to create the illusion of actual cannonballs.
Defines replacement for header.texture_weights (WHY?)


== Key-Bone Lookup ==
<source lang="cpp">
struct PADC {
    M2Array<M2TextureWeight> texture_weights;
}
</source>


*'''nKeyBoneLookup 16-bit shorts starting at ofsKeyBoneLookup.'''
M2InitParentAnimData: parentTextureWeights
Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 21 for the most models. At static models it is mostly 1.


'''Offset Type Name Description'''
===PSBC===
''0x00'' uint16 Bone Which bone. -1 if there is none.  
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}


--[[User:Schlumpf|schlumpf_]] 04:12, 21 August 2008 (CEST)
Defines ParentSequenceBounds
M2Array<M2Bounds> parentSequenceBounds;


== Bone Lookup Table ==
M2InitParentSequenceBoundsData: M2Bounds


*'''nBoneLookupTable 16-bit integers starting at ofsBoneLookupTable. (values: 0 to nBones-1)'''
===PEDC===
Lookup table for bones that transform geometry. Referenced in the various geoset definitions.
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}
M2Array<M2TrackBase> parentEventData;


'''Offset Type Name Description'''
M2InitParentEventData: eventTracks
''0x00'' uint16 Bone Which bone. -1 if there is none.


--[[User:Schlumpf|schlumpf_]] 03:10, 21 August 2008 (CEST)
===SKID===
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=This used to be filename based, using <tt>${basename}.skel</tt>. {{Unverified|Exact build unknown}}}}
uint32_t SKeletonfileID;    // links to [[M2/.skel]]


Geometry and rendering =
===TXID===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629|note=Replaces the filename for {{#Textures}} with hardcoded type.}}
Replaces in-file texture filenames.
  struct {
  uint32_t fileDataID;
} textureID[]


== Vertices ==
===LDV1===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629}}


*'''48 bytes per vertex (at ofsVertices [?])'''
Defines LodData


Models, too, use a Z-up coordinate systems, so in order to convert to Y-up, the X, Y, Z values become (X, -Z, Y).
<source lang="cpp">
'''Offset Type Description'''
struct LodData
0x00 float[3] Position
{
0x0C uint8[4] Bone weights (0 to 255)
  uint16 unk0;
0x10 uint8[4] Bone indices (0 to nBones-1)
  uint16 lodCount; //maxLod = lodCount-1
0x14 float[3] Normal vector
  float unk2_f;
0x20 float[2] Texture coordinates
  uint8_t particleBoneLod[4]; //lod serves as indes into this array
0x28 float[2] 0?
  _DWORD unk4;
};
</source>


The fields marked with ? probably have to do with skeletal animation.
Somehow defines _lod%0d.skin files. On pandarenfemale.m2, lodCount == 4. SFID has 7 files first 4 are ordinary .skin files and last 3 are _lod%0d.skin files. Enumeration for _lod%0d.skin files for that model starts from 1, and last file in SFID is pandarenfemale_lod03.skin So technically maxLod indeed represents maximum Lod


Considering "standard" GPU skeletal animation (matrix palette, 4 weights/bones per vertex):
unk2_f is used in formula, but it's purpose is unknown
*then "usually 255" should be an array of 4 unsigned chars defning the vertex weight for 4 bones
fmaxf(fminf(740.0 / unk2_f, 5.0), 0.5);
*then "vertex group" should be an Array of 4 unsigned chars indexing 4 bones ++++


LodData.particleBoneLod works this way: Each model has current lod which is [0..3]. Next:
<source lang="cpp">
  if ( lod < 1 )
    result = 0;
 
  if ( LodData)
    result = (0x10000 << LodData->particleBoneLod[lod]);
  else
    result = (0x10000 << (lod- 1));


-Note by Hergonan-
...
//For each ParticleEmitter and related M2Particle record
if ( result & M2CompBone[M2Particle->old.boneIndex].flags ) {
  //Do not animate this emitter
}
</source>


When I set all info for the vertices to 0 the visual part of the model didn't change but when I tried to select a model ingame by using the mouse and hovering over them I could only do that where the bones were. That means vertices are there for the mouse to react with.


==  Views (LOD) ==
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])


Every model has four of these, at 0x2C bytes each. I'm not sure if it is LOD because each one of them has the same amount of vertices for all four data sets. (???) First I thought this might be different vertex orderings from different views, to ease polygon sorting for transparent faces, but that's not it either. Hmm.
===RPID===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>recursion_model_filename</tt>}}


Starting at '''ofsViews''', there are four View (LOD?) header records followed by the data for all four.
  struct {
'''Offset Type Description'''
  uint32_t fileDataID; // referance a m2
0x00 uint32 number of elements in the index list
  } recursive_particle_models[particle count];
0x04 uint32 offset to the index list
0x08 uint32 number of elements in the triangle list (this is 3* the number of triangles to be drawn)
0x0C uint32 offset to the triangle list
0x10 uint32 number of elements in the vertex property list
0x14 uint32 offset to the vertex property list
0x18 uint32 number of elements in the submesh list
  0x1C uint32 offset to the submesh list
0x20 uint32 number of elements in the texture list
0x24 uint32 offset to the texture list
  0x28 uint32 LOD distance or something?


=== Indices ===
===GPID===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>geometry_model_filename</tt>}}


*'''A 16-bit unsigned integer per index, specifies vertices from the global vertex list.'''
struct {
  uint32_t fileDataID; // referance a m2
} geometry_particle_models[particle count];


===Triangles ===
===WFV1===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}
struct WFV1 {
  // unknown
};


*'''For every triangle, three 16-bit unsigned ints. These refer to the index list given above, not the global vertex list.'''
WFV1 = WaterFallVersion1
Tells that model has PBR-ish stuff and normal map. It uses separate render path from usual M2.


===Vertex properties ===
First tested on waterfall in 8.2.0 (fdid 2445860). And at that moment it was WFV1.


*'''4 bytes per vertex. Usually 0? Purpose unknown.'''
Later this same tech was used to create different models, which have no relation to waterfalls. But internally even the name "waterfall" was kept.


===Submeshes ===
Even stuff around areas in Shadowlands map is still called waterfall internally (fdid 3445776 for example). These models have WFV3 chunk.


*'''32 bytes per submesh. 48 bytes per submesh after client 2.0 (Burning Crusade expansion)'''
===WFV2===
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}
struct WFV2 {
  // unknown
};


'''Offset Type Description'''
===PGD1===
0x00 uint32 Mesh part ID
{{Template:SectionBox/VersionRange|min_expansionlevel=1|min_build=1.13.2.30172}}
0x04 uint16 Starting vertex number
ParticleGeosetData
0x06 uint16 Number of vertices
0x08 uint16 Starting triangle index (that's 3* the number of triangles drawn so far)
0x0A uint16 Number of triangle indices
0x0C uint16 Number of elements in the [[M2#Bone_Lookup_Table|bone lookup table]]
0x0E uint16 Starting index in the [[M2#Bone_Lookup_Table|bone lookup table]]
0x10 uint16 Unknown
0x12 uint16 root bone ?
0x14 float[3] Vector (3d)
''0x20 float[4] 4 float values? (only in Client 2.0 [[M2]]s)''


Mesh part ID: for character models, each hairstyle/thick armor/etc is present in the mesh, so to render a character with a specific set of looks, some of the submeshes should be ommitted based on this ID.
struct PGD1Entry {
    uint16_t geoset;
};
M2Array<PGD1Entry> p_g_d_v1; // count is equivalent to particle count


Reference to the [[M2#Bone_Lookup_Table|bone lookup table]]: the base number seems to increase per LOD, and the numbers in the [[M2#Bone_Lookup_Table|bone lookup table]], in turn, point to bone - indices to Block E. Maybe this could be an indicator of bones used in a submesh, or an animation sequence or something?
M2InitParticleGeosetData: m_emitterSelectionGroup


About LOD: Creatures\AncientProtector\AncientProtector.m2 is the first model I found where the LOD levels have a significant difference. The 4th LOD level still has the same number of vertices/polygons, but it has more submeshes (more sophisticated animation/lighting or something? I dunno)
This sets geoset for each Particle emitter. And with this value Particle Emitter start to obey same geoset rules as in M2SkinSection.


===Texture units ===
===WFV3===
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}
  struct WaterFallDataV3 {
    float bumpScale;  //Passed to vertex shader
    float value0_x;
    float value0_y;
    float value0_z;
    float value1_w;
    float value0_w;
    float value1_x;
    float value1_y;
    float value2_w;
    float value3_y;
    float value3_x;
    CImVector basecolor; // in rgba (not bgra)
    uint16_t flags;
    uint16_t unk0;
    float values3_w;
    float values3_z;
    float values4_y;
    float unk1;
    float unk2;
    float unk3;
    float unk4;
  }


'''24 bytes per record.''' More specifically, textures for each texture unit. Based on the current submesh number, one or two of these are used to determine the texture(s) to bind.
"value" fields are passed directly to fragment shader.


'''Offset Type Description'''
===PFDC===
0x00 uint16 Flags
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}
0x02 int16 Render order (used in skyboxes to ditch the need for depth buffering)
  [[PHYS|PHYS]] physics;
  0x04 uint16 Submesh index
  char PADDING[6]; // follows right after the last chunk in [[PHYS|PHYS]], or it could be data, only seen 0's
  0x06 uint16 Submesh index (repeated?)
                  // likely it isn't 6 but "whatever is needed until the next 8 (or 16?) byte alignment". --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]])
  0x08 int16 Color index or -1
Contains inline physics information in the same structure as the <tt>.phys</tt> files.
0x0A uint16 Index into render flags table
0x0C uint16 Texture unit number (0 or 1 - index into the texture unit lookup table)
0x0E uint16 always 1?
0x10 uint16 Texture to use (index into the texture lookup table)
0x12 uint16 Texture unit number (repeated?)
0x14 uint16 Transparency (index into transparency lookup table)
0x16 uint16 Texture animation (index into the texture animation lookup table)


Flags: usually 16 for static textures, and 0 for animated textures.
===EDGF===
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}
struct EDGF {
/*0x00*/ float _0x0[2];
/*0x08*/ float _0x8;
/*0x0C*/ char _0xC[0xC]
} edgf[];


==  Render flags ==
EdgeFade. This data is applied to mesh only if M2Batch.flags2 has 0x8 flag


*'''nRenderFlags (uint16, uint16) pairs starting at ofsRenderFlags'''
===NERF===
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}
struct NERF {
    C2Vector coefs;
};


'''Offset Description'''
Something related to calculation of alpha for whole model and related to distance. Components of coefs form ration with squared length of instanced model radius.
0x00 Flags
0x02 Blending mode


*'''Flags:'''
(coefs.x - squaredRadius) / (coefs.x - coefs.y)
'''Flag Meaning'''
0x01 Unlit
0x02 Unfogged?
0x04 Two-sided (no backface culling if set)
0x08 ?
0x10 Disable z-buffer?


*'''Blending mode'''
And this value is used as multiplier for model instace's alpha.
'''Value Meaning'''
0 Opaque
1 Alpha testing only
2 Alpha blending
3 Additive?
4 Additive alpha?
5 Modulate?
6 Used in the Deeprun Tram subway glass, supposedly (src=dest_color, dest=src_color) (?)


''Most of these blend values are taken from the MDL docs, but they sort of work (like additive blending for light shafts and such)''
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 08:35, 30 April 2020 (CEST)


== Texture unit lookup table ==
===DETL===
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34365}}
struct
{
  uint16_t flags;
  uint16_t packedFloat0;
  uint16_t packedFloat1; // multiplier for M2Light.diffuse_color
  uint16_t unk0;
  uint32_t unk1;
} DETL_recs[m2data.header.lights.count];


*'''nTexUnits 16-bit integers starting at ofsTexUnits. (values: -1, 0, 1)'''
Something related to lights


For models that use multitexturing, this maps given texture unit numbers into actual texture unit numbers (0 or 1).
===DBOC===
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}
struct
{
  float unk1_1;
  float unk1_2;
  uint32 unk1_3;
  uint32 unk1_4;
  float unk2_1;
  float unk2_2;
  uint32 unk2_3;
  uint32 unk2_4;
} DBOC;


Values of -1 seem to mean environment mapping.
Observed as 32 bytes in 9ARD_NebulaCloud_B01.m2, might be 2 entries, might not be.


One model is of special interest, Creature/KelThuzad/KelThuzad.m2, which is the only one that has an nL of 3, and has three texture units specified for some of its submeshes. Sure enough, two of those map to 0 and 1, and one maps to -1.
===AFRA===
{{Template:SectionBox/VersionRange|min_expansionlevel=10}}
Not observed in any files yet, presumably added in DF.


More confusion thanks to my favorite "weird" model, World/Generic/Gnome/Passive Doodads/GnomeMachine/GnomeSubwayGlass.m2, which is the translucent, environment mapped glass tunnel in the Deeprun Tram. It only has a single value in this block, -1, which is used for the single texture layer in both render operations in the model. This and the magic with rendering flags/blend modes make up the neat transparent-reflective glass effect, but confuse me even more about how envmapping and such is handled. (and where it seems to get the bluish color from - is it in the model (no color blocks in this particular model), the wmo, a solid background color, or simply the result of the blending used?)
=Skeleton and animation=
==Global sequences==
A list of timestamps that act as upper limits for global sequence ranges.
struct M2Loop
{
  uint32_t timestamp;
} loops[];


As a side note, on my (dated) system WoW does every texture unit in a single pass.  
==Standard animation block==
* {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}} uses a single-timeline approach, chaining all animations into one long piece and separating them via begin and end given in animation data. {{Template:Sandbox/VersionRange|min_expansionlevel=3}}, each animation has an own timeline.
* Animation blocks contain a list of lists of timestamps and a list of lists of values, where the first list is by animation and the second one by timestamp-entry.
*'''Many values that change with time are specified using blocks like the following.'''


== Replacable texture lookup ==
  template<typename T>
  struct M2Array
  {
  /*0x00*/  uint32_t number;
  /*0x04*/  uint32_t offset_elements;
  /*0x08*/
  };


*'''nK 16-bit integers starting at ofsK. (values: -1 or 0 to nTex-1)'''
  struct M2TrackBase
  {
  /*0x00*/  uint16_t interpolation_type;
  /*0x02*/  uint16_t global_sequence;
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}}
    M2Array<pair<uint32_t>> interpolation_ranges;  // no longer required {{Template:Sandbox/VersionRange|min_expansionlevel=3}}, as implicit by minimum and maximum timestamp per sequence.
    M2Array<uint32_t> timestamps;
#else
  /*0x04*/  M2Array<M2Array<uint32_t>> timestamps;
#endif
  /*0x0C*/
  };
 
  template<typename T>
  struct M2Track : M2TrackBase
  {
  /*0x00*/  // base
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}}
    M2Array<T> values;
#else
  /*0x0C*/  M2Array<M2Array<T>> values;
#endif
  /*0x14*/
  };


A reverse lookup table for 'replaced' textures, mapping replacable ids to texture indices or -1. Only goes up to the maximum id used in the model.
* Thus, as example, with
 
  {{Template:Type|M2CompBone|link=M2#Bones}} b;


--[[User:Schlumpf|schlumpf_]] 02:50, 21 August 2008 (CEST)
one may get the number of animations having translation information with


==  Colors and transparency ==
  b.translation.timestamps.number


=== Colors ===
and the number of timestamps in the first animation using


*'''nColors records of 0x38 bytes starting at ofsColors, followed by data referenced in these records.'''
  b.translation.timestamps.elements[0].number


For some swirling portals and volumetric lights, these define vertex colors. Referenced from the Texture Unit blocks in the LOD part. Contains a separate timeline for transparency values. If no animation is used, the given value is assumed to be constant.
and the first timestamp value of the first animation via
'''Offset Type Description'''
0x00 AnimationBlock (float,float,float) Data for RGB color values
0x1C AnimationBlock (short) Data for opacity values


Opacity values: 32767 = opaque, 0 = transparent.
  b.translation.timestamps.elements[0].elements[0]


===Transparency lookup table ===
The actual translation vector for animation 0 at timestamp 0 is at


*'''nTransLookup 16-bit integers starting at ofsTransLookup. (values: 0 to nH-1 ?)'''
  b.translation.values.elements[0].elements[0]


Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.
* Some timestamps/values.elements entries may have number/elements = 0, if for that animation id no animation is given.
* [[#.anim_files|.anim]] files are just a blob of data which may as well be in the main model file, that is pointed to by the first array_ref layer.
* [model file name][animation id]-[animation sub-id][[#.anim_files|.anim]]
* it seems like it is possible to detect if animation data is stored in-m2 or externally via
** All animations which have flags & 0x20 are stored internally.
** Animations which do not have flags & 0x20 are not stored internally.
** Animations which do not have flags & 0x20 AND do not have flags & 0x40 are in [[#.anim_files|.anim]] files
** Animations which do not have flags & 0x20 AND DO have flags 0x40 are stored... somewhere. I have no clue.


===Transparency ===
===Global Sequences===
If a block has a sequence >= 0, the block has a completely separate max timestamp. This is the value in the model's ofsGlobalSequences table; index into that table with this sequence value and use that as the block's max timestamp. Blocks that use these global sequences also only have one track, so at the same time as clipping the current timestamp to the max time above, interpolated value should always be taken from track 0 in the block.


*'''Specifies global transparency values''' (in addition to the values given in the Color block - I assume these are multiplied together eventually?)
A global sequence is completely unrelated to animations. It just always loops. This way, the sequence is not interrupted when an animation is launched.


nTransparency records of 0x1C bytes starting at ofsTransparency, followed by data referenced in these records.
This appears to be frequently used by models that don't have more conventional animations (login screen animations, items/weapons with animated effects, etc).
'''Offset Type Description'''
0x00 AnimationBlock (short) Data for transparency values


Transparency: 32767 for fully opaque, 0 for fully transparent
-- Rour, additionally, these sequences can be longer or shorter than whatever animation is running for a given model, so I recommend taking a global scene timestamp and then clipping that value into the given max timestamp range. Otherwise animations will appear to reset when the regular animation loops, which is not good.


== Textures ==
===Interpolation===
* If the interpolation type is 0, then values will change instantly at the timestamp, with no interpolation whatsoever.
* If the interpolation type is 1, then the block linearly interpolates between keyframe values (lerp for vectors/colours, nlerp for quaternions).
* If the interpolation type is 2, then cubic bezier spline interpolation is used.  This is only valid for M2SplineKey tracks.  When interpolating between two spline keys, the first control point is the first spline key's <code>value</code>, the second control point is the first spline key's <code>tanOut</code>, the third control point is the second spline key's <code>tanIn</code>, and the fourth control point is the second spline key's <code>value</code>.
* If the interpolation type is 3, then cubic hermite spline interpolation is used.  This is only valid for M2SplineKey tracks.  When interpolating between two spline keys, the starting point is the first spline key's <code>value</code>, the starting tangent is the first spline key's <code>tanOut</code>, the ending tangent is the second spline key's <code>tanIn</code>, and the ending point is the second spline key's <code>value</code>.


*'''Textures are defined globally in a list''', additionally, a lookup table is given, referenced during rendering, to select textures. 
'''NOTE:''' There is confusion about type <code>2</code> and <code>3</code> being <code>hermite/bezier</code> or <code>bezier/hermite</code>. Alpha says that <code>2 = hermite</code>, WoD says that <code>2 = bezier</code>. This was changed when the format went from MDL to M2. --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 01:53, 4 September 2017 (CEST), --[[User:Barncastle|Barncastle]] ([[User talk:Barncastle|talk]]) 19:15, 14 November 2018 (GMT)


=== Texture lookup table ===
In WotLK 2 appears to be bezier, which pretty much confirms it being bezier for wotlk+. TBC and classic need further checking though. -- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]]) 01:38, 16 June 2018 (CEST)


*'''Just a list of 16-bit integers. nTexLookup items starting at ofsTexLookup.'''
==.anim files==
{{Template:SectionBox/VersionRange|min_expansionlevel=3}}


===Texture definitions ===
Low priority sequences (e.g. emotes, one shot animations) are in extra files to allow for lazy loading. These files are raw data of timestamps and values for animation blocks. Blizzard's exporter prefers to align blocks to a 16 byte boundary, but that's not required.


*'''First is a list of nTextures texture definition records, 16 bytes per record, starting at ofsTextures.''' After it comes a string block with the texture filenames.
'''The client loads .anim files if (M2Sequence.flags & 0x130 ) == 0.''' The .anim file to use is <code>"%s%04d-%02d.anim" % (model_filename_without_extension, anim.id, anim.sub_anim_id)</code>.
'''Offset Type Description'''
0x00 uint32 Texture type (aka replacable id)
0x04 uint16 Unknown, usually 0
0x06 uint16 Flags
0x08 uint32 Filename length
0x0C uint32 Offset to filename


Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!) For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name, the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:
===Legion 24500===
In Legion, these files are optionally chunked now. They are chunked either
* if M2 header's 0x200000 flag is set and thus the new mid expansion format change is used
* if the M2 has a .skel file


*DBFilesClient\[[CharSections.dbc]]
For new format M2s, .anim is pretty much unchanged except that there is the AFM2 chunk header. The AFSA and AFSB chunks do not appear in that case. If it is a .skel file based model, the chunks are present and animation data is split into bone and attach data. The AFM2 chunk then contains the animation data for ????, the AFSA chunk that for attachments and the AFSB chunk that for bones. See .skel files for that.
*DBFilesClient\[[CreatureDisplayInfo.dbc]]
*DBFilesClient\[[ItemDisplayInfo.dbc]]
*(possibly more)


*Texture Types:
====AFM2====
'''Value Meaning'''
The same content as an old anim file would have. In fact, files that were just converted to the new format are bit identical except for the chunk header.
0 Texture given in filename
====AFSA====
1 Body + clothes
skeleton data for attachments
2 Cape
====AFSB====
6 Hair, beard
skeleton data for bones
8 Tauren fur
11 Skin for creatures
12 Skin for creatures #2
13 Skin for creatures #3


*Flags:
==Animation sequences==
  '''Value Meaning'''
List of animations present in the model.
  1 Texture wrap X
struct M2Sequence
  2 Texture wrap Y
{
''''Nothing changed here in WotLK so far!''''
/*0x00*/  uint16_t id;                  // Animation id in [[AnimationData.dbc]]
/*0x02*/  uint16_t variationIndex;      // Sub-animation id: Which number in a row of animations this one is.
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}
/*0x04*/  uint32_t start_timestamp;
/*0x08*/  uint32_t end_timestamp;
#else
/*0x04*/  uint32_t duration;            // The length of this animation sequence in milliseconds.
#endif
/*0x08*/  float movespeed;              // This is the speed the character moves with in this animation.
/*0x0c*/  uint32_t flags;                // See below.
  /*0x10*/  int16_t frequency;            // This is used to determine how often the animation is played. For all animations of the same type, this adds up to 0x7FFF (32767).
/*0x12*/  uint16_t _padding;
/*0x14*/  M2Range replay;                // May both be 0 to not repeat. Client will pick a random number of repetitions within bounds if given.
  #if version < ??? < 6.0.1
  /*0x1c*/  uint32_t blendTime;
#else
/*0x1c*/  uint16_t blendTimeIn;          // The client blends (lerp) animation states between animations where the end and start values differ. This specifies how long that blending takes. Values: 0, 50, 100, 150, 200, 250, 300, 350, 500.
/*0x1e*/  uint16_t blendTimeOut;        // The client blends between this sequence and the next sequence for blendTimeOut milliseconds.
#endif
                                  // For both blendTimeIn and blendTimeOut, the client plays both sequences simultaneously while interpolating between their animation transforms.
  M2Bounds bounds;
/*0x20*/  int16_t variationNext;        // id of the following animation of this AnimationID, points to an Index or is -1 if none.
/*0x22*/  uint16_t aliasNext;            // id in the list of animations. Used to find actual animation if this sequence is an alias (flags & 0x40)
/*0x24*/
} sequences[];


===Texture animation lookup table ===
--[[User:Koward|Koward]] ([[User talk:Koward|talk]]) 09:50, 18 December 2015 (UTC) In M2 v274 (Legion), it looks like blend_time has been divided in two uint16_t, and for standard animations the old blend_time is duplicated in both fields (ex : uint32 150 becomes two uint16 150). Maybe start and end blend_time values ? See Creature/GennGreymane/GennGreymane.m2 .
===Flags===
''One thing I saw in the source is that "-1 animationblocks" in bones wont get parsed if 0x20 is not set.''


*'''nTexAnimLookup 16-bit integers starting at ofsTexAnimLookup. (values: -1, 0 to nTexAnims-1)'''
{| class="wikitable"
! width="70" | Flag !! width="500" | Description
|-
| 0x01 || Sets 0x80 when loaded. (M2Init)
|-
| 0x02 ||
|-
| 0x04 ||
|-
| 0x08 ||
|-
| 0x10 || apparently set during runtime in CM2Shared::LoadLowPrioritySequence for all entries of a loaded sequence (including aliases)
|-
| 0x20 || primary bone sequence -- If set, the animation data is in the .m2 file.  If not set, the animation data is in an .anim file. {{Template:Unverified|Was named 'looped animation' by schlumpf years ago, without source.}}
|-
| 0x40 || has next / is alias (To find the animation data, the client skips these by following aliasNext until an animation without 0x40 is found.)
|-
| 0x80 || Blended animation (if either side of a transition has 0x80, lerp between end->start states, unless end==start by comparing bone values)
|-
| 0x100 || sequence stored in model ?
|-
| 0x200 || Signals that uint32_t blendTime is two fields: blendTimeIn and blendTimeOut. (Actual usage and influence of blendTimeIn and blendTimeOut depends on blendTimeOperation, which is not stored in files, but code and context dependent)
|-
| 0x400 ||
|-
| 0x800 || seen in Legion 24500 models
|-
| 0x1000 ||
|-
| 0x2000 ||
|-
|}


Contain indices into the texture animations list, or -1 meaning a static texture.
-- Rour, some animations rely on blending to look right. The MoP mage CM shoulders only animate half of their movement and rely on lerping back to the start position to look correct.


= Effects =
=== Animation Lookup===
Hash table for Animations in [[AnimationData.dbc]]. For a list of animation names, see [[M2/AnimationList]].
   
   
== Ribbon emitters ==
struct
{
  int16_t animation_index; // Index at [[#Animation_sequences|ofsAnimations]] which represents the animation in [[AnimationData.dbc]]. -1 if none.
} animation_lookups[];
 
The hash used is <tt>anim_id % num_buckets</tt>. If a bucket is used, a stride of <tt>n^2</tt> is added with <tt>n = 1, 2, …</tt> until the entry is matching:
 
<source lang="c++">
M2Sequence* find_entry (uint32_t anim_id)
{
  size_t i (anim_id % animation_lookups.count);
 
  for (size_t stride (1); true; ++stride)
  {
    if (animation_lookups[i] == -1)
    {
      return nullptr;
    }
    if (animation_sequences[animation_lookups[i]].id == anim_id)
    {
      return &animation_sequences[i];
    }
 
    i = (i + stride * stride) % animation_lookups.count;
    // so original_i + 1, original_i + 1 + 4, original_i + 1 + 4 + 9, …
  }
 
  [[unreachable]];
}
</source>
 
The entry referenced is the first in the `nextAlias` chain of a given animation id. Thus, <tt>num_buckets < num_animations</tt>, even if a model would have all animations multiple times.
 
=== Playable Animation Lookup ===
{{Template:SectionBox/VersionRange|max_expansionlevel=2|note=Partially inlined into M2Sequences}}
 
Lookup table for Playable Animation in [[AnimationData.dbc]]
 
'''Offset  Type    Description'''
0x00  int16    Fallback Animation ID in [[AnimationData.dbc]]
0x02  int16    Flags (0, 1, 3 seen)
 
Models don't present all animations sequences. This table maps from global animation list to available animation for the current model. The engine uses it to know which animation clip to play. That's why there are a lot of zeros ("Stand") for creatures.
 
Flags are used to modify how the clip should be played:
'''Value      Meaning'''
0      Play normal
1      Play backwards?
3      Freeze
 
For instance, the "HumanMale/HumanMale.m2" model has just one "Loot" animation sequence. "LootHold" and "LootUp" are obtained with flags 3 and 1.
 
==  Bones ==
struct M2CompBone                // probably M2Bone {{Template:Sandbox/VersionRange|max_expansionlevel=1}}
{
  int32_t key_bone_id;            // Back-reference to [[#Key-Bone_Lookup|the key bone lookup table]]. -1 if this is no key bone.
  enum
  {
    ignoreParentTranslate = 0x1,
    ignoreParentScale = 0x2,
    ignoreParentRotation = 0x4,
    spherical_billboard = 0x8,
    cylindrical_billboard_lock_x = 0x10,
    cylindrical_billboard_lock_y = 0x20,
    cylindrical_billboard_lock_z = 0x40,
    transformed = 0x200,
    kinematic_bone = 0x400,      // MoP+: allow [[PHYS|physics]] to influence this bone
    helmet_anim_scaled = 0x1000,  // set blend_modificator to [[HelmetAnimScaling.dbc|helmetAnimScalingRec]].m_amount for this bone
    something_sequence_id = 0x2000, // <=bfa+, parent_bone+submesh_id are a sequence id instead?!
  };
  uint32_t flags;               
  int16_t parent_bone;            // Parent bone ID or -1 if there is none.
  uint16_t [[M2/.skin#Submeshes|submesh_id]];            // Mesh part ID OR uDistToParent?
  union {                        // only {{Template:Sandbox/VersionRange|min_expansionlevel=2}}?
    struct {
      uint16_t uDistToFurthDesc;
      uint16_t uZRatioOfChain;
    } CompressData;              // {{Template:Unverified|No model has ever had this part of the union used.}}
    uint32_t boneNameCRC;        // these are for debugging only. their bone names match those in key bone lookup.
  };
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;
#if {{Template:Sandbox/VersionRange|max_expansionlevel=1}}
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation;
#else
  [[#Standard_animation_block|M2Track]]<{{Template:Type|M2CompQuat|link=Quaternion_values_and_2.x}}> rotation;  // compressed values, default is (32767,32767,32767,65535) == (0,0,0,1) == identity
#endif
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scale;
  {{Template:Type|C3Vector}} pivot;                // The pivot point of that bone.
} bones[];
The bone indices in the vertex definitions seem to index into this data.
 
===Billboards===
The billboarding bits are used for various things:
* Light halos around lamps must always face the viewer
* The cannonball stack model (in the Deadmines or Booty Bay), where each cannonball is a crude hemisphere, they always face the viewer to create the illusion of actual cannonballs.
 
Spherical and cylindrical billboard bits are mutually exclusive. Only one of them can be used for the bone.
 
===Bone Lookup Table===
Lookup table for bones referenced from [[M2/.skin#Submeshes|M2SkinSection]].
struct
{
  uint16_t bone;
} bone_lookup_table[];
 
===Key-Bone Lookup===
Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 27 for the most models up to WotLK, then 35, whatever the biggest named key bone was at the given expansion. For static models it is mostly 1.
struct
{
  uint16_t bone; // -1 if none
} key_bone_lookup[];
 
==== Key Bone Names====
<div class="mw-collapsible mw-collapsed toccolours">
The key bone name table is collapsed by default to reduce page length. Click expand on the right to expand.
<div class="mw-collapsible-content" id="mw-customcollapsible-myDivision">
Confirmed names (matching CRC) {{Verified|highlighted in green}}, unverified/made up names are unmarked.
{| style='width: 40%'
! ID !! Name !! Notes
|-
| 00 || {{Verified|ArmL}} ||
|-
| 01 || {{Verified|ArmR}} ||
|-
| 02 || {{Verified|ShoulderL}} ||
|-
| 03 || {{Verified|ShoulderR}} ||
|-
| 04 || {{Verified|SpineLow}} || "Upper Body" in animkits, if not present use Head
|-
| 05 || {{Verified|Waist}} ||
|-
| 06 || {{Verified|Head}} ||
|-
| 07 || {{Verified|Jaw}} ||
|-
| 08 || {{Verified|IndexFingerR}} ||
|-
| 09 || {{Verified|MiddleFingerR}} ||
|-
| 10 || {{Verified|PinkyFingerR}} ||
|-
| 11 || {{Verified|RingFingerR}} ||
|-
| 12 || {{Verified|ThumbR}} ||
|-
| 13 || {{Verified|IndexFingerL}} ||
|-
| 14 || {{Verified|MiddleFingerL}} ||
|-
| 15 || {{Verified|PinkyFingerL}} ||
|-
| 16 || {{Verified|RingFingerL}} ||
|-
| 17 || {{Verified|ThumbL}} ||
|-
| 18 || {{Verified|$BTH}} ||
|-
| 19 || {{Verified|$CSR}} ||
|-
| 20 || {{Verified|$CSL}} ||
|-
| 21 || {{Verified|_Breath}} ||
|-
| 22 || {{Verified|_Name}} ||
|-
| 23 || {{Verified|_NameMount}} ||
|-
| 24 || {{Verified|$CHD}} ||
|-
| 25 || {{Verified|$CCH}} ||
|-
| 26 || {{Verified|Root}} ||
|-
| 27 || {{Verified|Wheel1}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 28 || {{Verified|Wheel2}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 29 || {{Verified|Wheel3}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 30 || {{Verified|Wheel4}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 31 || {{Verified|Wheel5}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 32 || {{Verified|Wheel6}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 33 || {{Verified|Wheel7}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
|-
| 34 || {{Verified|Wheel8}} || {{Template:Sandbox/VersionRange|min_expansionlevel=3}}, higher bone IDs not present in WotLK
|-
| 35 || {{Verified|FaceAttenuation}} ||
|-
| 36 || {{Verified|EXP_C1_Cape1}} || Previous unconfirmed name was "CapeParent"
|-
| 37 || {{Verified|EXP_C1_Cape2}} || Previous unconfirmed name was "CapeChild1"
|-
| 38 || {{Verified|EXP_C1_Cape3}} || Previous unconfirmed name was "CapeChild2"
|-
| 39 || {{Verified|EXP_C1_Cape4}} || Previous unconfirmed name was "CapeChild3"
|-
| 40 || {{Verified|EXP_C1_Cape5}} || Previous unconfirmed name was "CapeChild4"
|-
| 41 || {{Verified|EXP_C1_Tail1}} ||
|-
| 42 || {{Verified|EXP_C1_Tail2}} ||
|-
| 43 || {{Verified|EXP_C1_LoinBk1}} || Previous unconfirmed name was "TabardParent"
|-
| 44 || {{Verified|EXP_C1_LoinBk2}}  ||  Previous unconfirmed name was "TabardChild1"
|-
| 45 || {{Verified|EXP_C1_LoinBk3}} ||  Previous unconfirmed name was "TabardChild2"
|-
| 48 || {{Verified|EXP_C1_Spine2}} || Previous unconfirmed name was "UpperBodyParent"
|-
| 49 || {{Verified|EXP_C1_Neck1}} || Previous unconfirmed name was "NeckParent"
|-
| 50 || {{Verified|EXP_C1_Neck2}} || Previous unconfirmed name was "NeckChild1"
|-
| 51 || {{Verified|EXP_C1_Pelvis1}} || Previous unconfirmed name was "LowerBodyParent"
|-
| 52 || {{Verified|Buckle}} ||
|-
| 53 || {{Verified|Chest}} ||
|-
| 54 || {{Verified|Main}} ||
|-
| 55 || {{Verified|EXP_R1_Leg1Twist1}} || Previous unconfirmed name was "LegR"
|-
| 56 || {{Verified|EXP_L1_Leg1Twist1}} || Previous unconfirmed name was "LegL"
|-
| 57 || {{Verified|EXP_R1_Leg2Twist1}} || Previous unconfirmed name was "KneeR"
|-
| 58 || {{Verified|EXP_L1_Leg2Twist1}} || Previous unconfirmed name was "KneeL"
|-
| 59 || {{Verified|FootL}} ||
|-
| 60 || {{Verified|FootR}} ||
|-
| 61 || {{Verified|ElbowR}} ||
|-
| 62 || {{Verified|ElbowL}} ||
|-
| 63 || {{Verified|EXP_L1_Shield1}} || Previous unconfirmed name was "Unk_ElbowL_Child"
|-
| 64 || {{Verified|HandR}} ||
|-
| 65 || {{Verified|HandL}} ||
|-
| 66 || {{Verified|WeaponR}} ||
|-
| 67 || {{Verified|WeaponL}} ||
|-
| 68 || {{Verified|SpellHandL}} || Previous unconfirmed name was "Unk_WristL_Child"
|-
| 69 || {{Verified|SpellHandR}} || Previous unconfirmed name was "Unk_WristR_Child"
|-
| 70 || {{Verified|EXP_R1_Leg1Twist3}} || Previous unconfirmed name was "KneeR_UpperRig"
|-
| 71 || {{Verified|EXP_L1_Leg1Twist3}} || Previous unconfirmed name was "KneeL_UpperRig"
|-
| 72 || {{Verified|EXP_R1_Arm1Twist2}} || Previous unconfirmed name was "ArmR_2"
|-
| 73 || {{Verified|EXP_L1_Arm1Twist2}} || Previous unconfirmed name was "ArmL_2"
|-
| 74 || {{Verified|EXP_R1_Arm1Twist3}} || Previous unconfirmed name was "ElbowR_UpperRig"
|-
| 75 || {{Verified|EXP_L1_Arm1Twist3}} || Previous unconfirmed name was "ElbowL_UpperRig"
|-
| 76 || {{Verified|EXP_R1_Arm2Twist2}} || Previous unconfirmed name was "ForearmR"
|-
| 77 || {{Verified|EXP_L1_Arm2Twist2}} || Previous unconfirmed name was "ForearmL"
|-
| 78 || {{Verified|EXP_R1_Arm2Twist3}} || Previous unconfirmed name was "WristR_UpperRig"
|-
| 79 || {{Verified|EXP_L1_Arm2Twist3}} || Previous unconfirmed name was "WristL_UpperRig"
|-
| 80 || {{Verified|ForearmR}} ||
|-
| 81 || {{Verified|ForearmL}} ||
|-
| 82 || {{Verified|EXP_R1_Arm1Twist1}} ||
|-
| 83 || {{Verified|EXP_L1_Arm1Twist1}} ||
|-
| 84 || {{Verified|EXP_R1_Arm2Twist1}} ||
|-
| 85 || {{Verified|EXP_L1_Arm2Twist1}} ||
|-
| 86 || {{Verified|EXP_R1_FingerClawA1}} ||
|-
| 87 || {{Verified|EXP_R1_FingerClawB1}} ||
|-
| 88 || {{Verified|EXP_L1_FingerClawA1}} ||
|-
| 89 || {{Verified|EXP_L1_FingerClawB1}} ||
|-
| 190 || {{Verified|_BackCloak}} ||
|-
| 191 || {{Verified|face_hair_00_M_JNT}} ||
|-
| 192 || {{Verified|face_beard_00_M_JNT}} ||
|-
| 193 || {{Verified|face_cheek_02_L_SkinPoint}} || IGC (in-game cinematic) models only
|-
| 194 || {{Verified|face_cheek_02_R_SkinPoint}} || IGC models only
|-
| 195 || {{Verified|face_eyeCornerIn_00_L_SkinPoint}} || IGC models only
|-
| 196 || {{Verified|face_eyeCornerIn_00_R_SkinPoint}} || IGC models only
|-
| 197 || {{Verified|face_eyeCornerOut_00_L_SkinPoint}} || IGC models only
|-
| 198 || {{Verified|face_eyeCornerOut_00_R_SkinPoint}} || IGC models only
|-
| 199 || {{Verified|face_eyebrow_00_L_SkinPoint}} || IGC models only
|-
| 200 || {{Verified|face_eyebrow_00_M_SkinPoint}} || IGC models only
|-
| 201 || {{Verified|face_eyebrow_00_R_SkinPoint}} || IGC models only
|-
| 202 || {{Verified|face_eyebrow_01_L_SkinPoint}} || IGC models only
|-
| 203 || {{Verified|face_eyebrow_01_R_SkinPoint}} || IGC models only
|-
| 204 || {{Verified|face_eyebrow_02_L_SkinPoint}} || IGC models only
|-
| 205 || {{Verified|face_eyebrow_02_R_SkinPoint}} || IGC models only
|-
| 206 || {{Verified|face_eyebrow_03_L_SkinPoint}} || IGC models only
|-
| 207 || {{Verified|face_eyebrow_03_R_SkinPoint}} || IGC models only
|-
| 208 || {{Verified|face_eyelidBot_00_L_SkinPoint}} || IGC models only
|-
| 209 || {{Verified|face_eyelidBot_00_R_SkinPoint}} || IGC models only
|-
| 210 || {{Verified|face_eyelidBot_01_L_SkinPoint}} || IGC models only
|-
| 211 || {{Verified|face_eyelidBot_01_R_SkinPoint}} || IGC models only
|-
| 212 || {{Verified|face_eyelidBot_02_L_SkinPoint}} || IGC models only
|-
| 213 || {{Verified|face_eyelidBot_02_R_SkinPoint}} || IGC models only
|-
| 214 || {{Verified|face_eyelidTop_00_L_SkinPoint}} || IGC models only
|-
| 215 || {{Verified|face_eyelidTop_00_R_SkinPoint}} || IGC models only
|-
| 216 || {{Verified|face_eyelidTop_01_L_SkinPoint}} || IGC models only
|-
| 217 || {{Verified|face_eyelidTop_01_R_SkinPoint}} || IGC models only
|-
| 218 || {{Verified|face_eyelidTop_02_L_SkinPoint}} || IGC models only
|-
| 219 || {{Verified|face_eyelidTop_02_R_SkinPoint}} || IGC models only
|-
| 220 || {{Verified|face_noseBridge_00_L_SkinPoint}} || IGC models only
|-
| 221 || {{Verified|face_noseBridge_00_R_SkinPoint}} || IGC models only
|-
| 222 || {{Verified|face_overEye_00_L_SkinPoint}} || IGC models only
|-
| 223 || {{Verified|face_overEye_00_R_SkinPoint}} || IGC models only
|-
| 224 || {{Verified|face_overOuterEye_00_L_SkinPoint}} || IGC models only
|-
| 225 || {{Verified|face_overOuterEye_00_R_SkinPoint}} || IGC models only
|-
| 226 || {{Verified|face_underEye_00_L_SkinPoint}} || IGC models only
|-
| 227 || {{Verified|face_underEye_00_R_SkinPoint}} || IGC models only
|-
| 228 || {{Verified|face_cheekPuff_00_L_SkinPoint}} || IGC models only
|-
| 229 || {{Verified|face_cheekPuff_00_R_SkinPoint}} || IGC models only
|-
| 230 || {{Verified|face_cheek_00_L_SkinPoint}} || IGC models only
|-
| 231 || {{Verified|face_cheek_00_R_SkinPoint}} || IGC models only
|-
| 232 || {{Verified|face_cheek_01_L_SkinPoint}} || IGC models only
|-
| 233 || {{Verified|face_cheek_01_R_SkinPoint}} || IGC models only
|-
| 234 || {{Verified|face_chin_00_L_SkinPoint}} || IGC models only
|-
| 235 || {{Verified|face_chin_00_M_SkinPoint}} || IGC models only
|-
| 236 || {{Verified|face_chin_00_R_SkinPoint}} || IGC models only
|-
| 237 || {{Verified|face_ear_00_L_SkinPoint}} || IGC models only
|-
| 238 || {{Verified|face_ear_00_R_SkinPoint}} || IGC models only
|-
| 239 || {{Verified|face_jaw_01_M_SkinPoint}} || IGC models only
|-
| 240 || {{Verified|face_jowl_00_L_SkinPoint}} || IGC models only
|-
| 241 || {{Verified|face_jowl_00_R_SkinPoint}} || IGC models only
|-
| 242 || {{Verified|face_jowl_01_L_SkinPoint}} || IGC models only
|-
| 243 || {{Verified|face_jowl_01_R_SkinPoint}} || IGC models only
|-
| 244 || {{Verified|face_lipBotBase_00_M_SkinPoint}} || IGC models only
|-
| 245 || {{Verified|face_lipTopBase_00_M_SkinPoint}} || IGC models only
|-
| 246 || {{Verified|face_mouthCorner_00_L_SkinPoint}} || IGC models only
|-
| 247 || {{Verified|face_mouthCorner_00_R_SkinPoint}} || IGC models only
|-
| 248 || {{Verified|face_mouthCurlBot_00_M_SkinPoint}} || IGC models only
|-
| 249 || {{Verified|face_mouthCurlTop_00_M_SkinPoint}} || IGC models only
|-
| 250 || {{Verified|face_mouth_00_M_SkinPoint}} || IGC models only
|-
| 251 || {{Verified|face_nasLab_00_L_SkinPoint}} || IGC models only
|-
| 252 || {{Verified|face_nasLab_00_R_SkinPoint}} || IGC models only
|-
| 253 || {{Verified|face_nasLab_01_L_SkinPoint}} || IGC models only
|-
| 254 || {{Verified|face_nasLab_01_R_SkinPoint}} || IGC models only
|-
| 255 || {{Verified|face_noseBase_00_M_SkinPoint}} || IGC models only
|-
| 256 || {{Verified|face_sneerDriver_00_L_SkinPoint}} || IGC models only
|-
| 257 || {{Verified|face_sneerDriver_00_R_SkinPoint}} || IGC models only
|-
| 258 || {{Verified|face_sneerLower_00_L_SkinPoint}} || IGC models only
|-
| 259 || {{Verified|face_sneerLower_00_R_SkinPoint}} || IGC models only
|-
| 260 || {{Verified|face_sneer_00_L_SkinPoint}} || IGC models only
|-
| 261 || {{Verified|face_sneer_00_R_SkinPoint}} || IGC models only
|-
| 262 || {{Verified|face_teethBot_00_M_SkinPoint}} || IGC models only
|-
| 263 || {{Verified|face_teethTop_00_M_SkinPoint}} || IGC models only
|-
| 264 || {{Verified|face_tongue_00_M_SkinPoint}} || IGC models only
|-
| 265 || {{Verified|root_main_00_M_SkinPoint}} || IGC models only
|-
| 266 || {{Verified|spine_mainBendy_00_M_SkinPoint}} || IGC models only
|-
| 267 || {{Verified|clavicle_main_00_L_SkinPoint}} || IGC models only
|-
| 268 || {{Verified|arm_shoulderBendy_00_L_SkinPoint}} || IGC models only
|-
| 269 || {{Verified|hand_main_00_L_JNT}} ||
|-
| 270 || {{Verified|hand_index_00_L_SkinPoint}} || IGC models only
|-
| 271 || {{Verified|hand_main_00_L_SkinPoint}} || IGC models only
|-
| 272 || {{Verified|hand_ring_00_L_SkinPoint}} || IGC models only
|-
| 273 || {{Verified|hand_pinky_00_L_SkinPoint}} || IGC models only
|-
| 274 || {{Verified|hand_thumb_00_L_SkinPoint}} || IGC models only
|-
| 275 || {{Verified|clavicle_main_00_R_SkinPoint}} || IGC models only
|-
| 276 || {{Verified|arm_shoulderBendy_00_R_SkinPoint}} || IGC models only
|-
| 277 || {{Verified|hand_main_00_R_JNT}} ||
|-
| 278 || {{Verified|hand_main_00_R_SkinPoint}} || IGC models only
|-
| 279 || {{Verified|hand_middle_00_R_SkinPoint}} || IGC models only
|-
| 280 || {{Verified|hand_ring_00_R_SkinPoint}} || IGC models only
|-
| 281 || {{Verified|hand_pinky_00_R_SkinPoint}} || IGC models only
|-
| 282 || {{Verified|hand_thumb_00_R_SkinPoint}} || IGC models only
|-
| 283 || {{Verified|head_main_00_M_SkinPoint}} || IGC models only
|-
| 284 || {{Verified|face_jaw_00_M_SkinPoint}} || IGC models only
|-
| 285 || {{Verified|EXP_L1_Eye1}} ||
|-
| 286 || {{Verified|EXP_R1_Eye1}} ||
|-
| 287 || {{Verified|EXP_L1_EyeLid1}} ||
|-
| 288 || {{Verified|EXP_R1_EyeLid1}} ||
|-
| 289 || {{Verified|EXP_L1_EyeLid2}} ||
|-
| 290 || {{Verified|EXP_R1_EyeLid2}} ||
|-
| 292 || {{Verified|EXP_L1_WingArm1Twist1}} ||
|-
| 293 || {{Verified|EXP_R1_WingArm1Twist1}} ||
|-
| 296 || {{Verified|waterfall_top_sound}} ||
|-
| 297 || {{Verified|waterfall_bottom_sound}} ||
|}
</div></div>
 
====Non-Key Bone Names====
The below list is confirmed bone names (the CRC for these still appears in models), although they aren't used as key bones.
 
<div class="mw-collapsible mw-collapsed toccolours">
The non-key bone name table is collapsed by default to reduce page length. Click expand on the right to expand.
<div class="mw-collapsible-content" id="mw-customcollapsible-myDivision">
{| style='width: 20%'
! Name !! CRC
|-
| $BWA || <tt>0x892b044e</tt>
|-
| $BWP || <tt>0xe39b24bc</tt>
|-
| $BWR || <tt>0x0d954590</tt>
|-
| $BWS || <tt>0x7a927506</tt>
|-
| $CAH || <tt>0xedad630a</tt>
|-
| $CPP || <tt>0xad18d84c</tt>
|-
| $CSS || <tt>0x1f3cda35</tt>
|-
| $CST || <tt>0x81584f96</tt>
|-
| $DTH || <tt>0xdf57939b</tt>
|-
| $ESD || <tt>0x98622340</tt>
|-
| $FD1 || <tt>0xbfc89c3c</tt>
|-
| $FL0 || <tt>0x001626a2</tt>
|-
| $FR0 || <tt>0xd457197d</tt>
|-
| $FSD || <tt>0x9a249d19</tt>
|-
| $HIT || <tt>0x3d205aac</tt>
|-
| $SCD || <tt>0xca0beed3</tt>
|-
| $SHL || <tt>0x2724bf2a</tt>
|-
| $SHR || <tt>0xdd2b8249</tt>
|-
| $TRD || <tt>0x9c9ddb46</tt>
|-
| -Blid_L01 || <tt>0x952640dd</tt>
|-
| -EF_Eyelid01 || <tt>0xff05e0fc</tt>
|-
| -EF_Eyelid_Death || <tt>0x91249ce5</tt>
|-
| -Eye01 || <tt>0x4c50b066</tt>
|-
| -Flid_R01 || <tt>0x07d4c05d</tt>
|-
| ankle_L || <tt>0x1114bd09</tt>
|-
| ankle_R || <tt>0xeb1b806a</tt>
|-
| arm_L || <tt>0xa100d5a4</tt>
|-
| arm_R || <tt>0x5b0fe8c7</tt>
|-
| B_Loin_01 || <tt>0x46c1b68f</tt>
|-
| B_Loin_02 || <tt>0xdfc8e735</tt>
|-
| Belly || <tt>0x49b52472</tt>
|-
| Blid_L || <tt>0xe42907dd</tt>
|-
| Blid_R || <tt>0x1e263abe</tt>
|-
| CalfL || <tt>0x57ebb63c</tt>
|-
| CalfR || <tt>0xade48b5f</tt>
|-
| CheekL || <tt>0x2acb54c0</tt>
|-
| CheekR || <tt>0xd0c469a3</tt>
|-
| ChestL || <tt>0xff3a859c</tt>
|-
| ChestR || <tt>0x0535b8ff</tt>
|-
| dSpine1_joint || <tt>0xfdba68c8</tt>
|-
| Ear_L_01 || <tt>0xed8d18a0</tt>
|-
| Ear_L_02 || <tt>0x7484491a</tt>
|-
| Ear_R_01 || <tt>0x5d4b380c</tt>
|-
| Ear_R_02 || <tt>0xc44269b6</tt>
|-
| elbow_L || <tt>0xa8948290</tt>
|-
| elbow_R || <tt>0x529bbff3</tt>
|-
| eye_L || <tt>0x8d92a5dd</tt>
|-
| eye_R || <tt>0x779d98be</tt>
|-
| EyeBowL || <tt>0x9b22dfb5</tt>
|-
| EyeBowR || <tt>0x612de2d6</tt>
|-
| EyebrowL || <tt>0x5bcd3a5c</tt>
|-
| EyebrowR || <tt>0xa1c2073f</tt>
|-
| F_Loin_01 || <tt>0x1b2de783</tt>
|-
| F_Loin_02 || <tt>0x8224b639</tt>
|-
| F_Loin_03 || <tt>0xf52386af</tt>
|-
| fin || <tt>0xad2ef231</tt>
|-
| finger_L || <tt>0x6da04d3b</tt>
|-
| finger_R || <tt>0x97af7058</tt>
|-
| FinLeft_joint50 || <tt>0x4eb9c4de</tt>
|-
| FinRight_joint51 || <tt>0x9d4d0569</tt>
|-
| Flid_L || <tt>0x7fb845cb</tt>
|-
| Flid_R || <tt>0x85b778a8</tt>
|-
| foot_L || <tt>0x06f9c681</tt>
|-
| foot_R || <tt>0xfcf6fbe2</tt>
|-
| FootBackL || <tt>0xbd5cfe0e</tt>
|-
| FootBackR || <tt>0x4753c36d</tt>
|-
| FootFrontL || <tt>0xad3efd23</tt>
|-
| FootFrontR || <tt>0x5731c040</tt>
|-
| Geo_EyeLid_Death || <tt>0x35d090eb</tt>
|-
| GEO_EyelidL || <tt>0x428bb93e</tt>
|-
| GEO_EyelidR || <tt>0xb884845d</tt>
|-
| hand_L || <tt>0x2759dd85</tt>
|-
| hand_R || <tt>0xdd56e0e6</tt>
|-
| HeadScale_joint22 || <tt>0x9b0a86ea</tt>
|-
| hip_L || <tt>0x3ff65768</tt>
|-
| hip_R || <tt>0xc5f96a0b</tt>
|-
| HipL || <tt>0x68b72b2c</tt>
|-
| HipR || <tt>0x92b8164f</tt>
|-
| Hips || <tt>0xded10611</tt>
|-
| Hump || <tt>0xad8861a3</tt>
|-
| IndexFingerTipL || <tt>0x76451c91</tt>
|-
| IndexFingerTipR || <tt>0x8c4a21f2</tt>
|-
| IndexL01 || <tt>0x135d4e78</tt>
|-
| IndexL02 || <tt>0x8a541fc2</tt>
|-
| IndexL03 || <tt>0xfd532f54</tt>
|-
| IndexR01 || <tt>0x05e5c002</tt>
|-
| IndexR02 || <tt>0x9cec91b8</tt>
|-
| IndexR03 || <tt>0xebeba12e</tt>
|-
| JawBottomL || <tt>0xa914ce26</tt>
|-
| JawBottomR || <tt>0x531bf345</tt>
|-
| JawTopL || <tt>0xa2fc75cc</tt>
|-
| JawTopR || <tt>0x58f348af</tt>
|-
| knee_L || <tt>0xf55ed5cc</tt>
|-
| knee_R || <tt>0x0f51e8af</tt>
|-
| KneeL || <tt>0x1b0485e7</tt>
|-
| KneeR || <tt>0xe10bb884</tt>
|-
| Leg01_Back_L || <tt>0x2755d694</tt>
|-
| Leg01_Back_R || <tt>0xdd5aebf7</tt>
|-
| Leg01_Front_R || <tt>0x76f67c58</tt>
|-
| Leg01_Middle_R || <tt>0x239aad94</tt>
|-
| Leg01Front_L || <tt>0x8b498ad5</tt>
|-
| Leg01Middle_L || <tt>0x9e20ef43</tt>
|-
| Leg02_Back_L || <tt>0xa9dad177</tt>
|-
| Leg02_Back_R || <tt>0x53d5ec14</tt>
|-
| Leg02_Front_R || <tt>0x4f7b409d</tt>
|-
| Leg02_Middle_L || <tt>0x32a22bf4</tt>
|-
| Leg02_Middle_R || <tt>0xc8ad1697</tt>
|-
| Leg02Front_L || <tt>0x05c68d36</tt>
|-
| Leg03_Back_L || <tt>0x6570d1e9</tt>
|-
| Leg03_Back_R || <tt>0x9f7fec8a</tt>
|-
| Leg03_Front_R || <tt>0x580054de</tt>
|-
| Leg03_Middle_L || <tt>0xdd6040ca</tt>
|-
| Leg03_Middle_R || <tt>0x276f7da9</tt>
|-
| Leg03Front_L || <tt>0xc96c8da8</tt>
|-
| LegBackL01 || <tt>0x7e47654c</tt>
|-
| LegBackL02 || <tt>0xe74e34f6</tt>
|-
| LegBackR01 || <tt>0x68ffeb36</tt>
|-
| LegBackR02 || <tt>0xf1f6ba8c</tt>
|-
| LegFrontL01 || <tt>0xe0f912f0</tt>
|-
| LegFrontL02 || <tt>0x79f0434a</tt>
|-
| LegFrontR01 || <tt>0xf6419c8a</tt>
|-
| LegFrontR02 || <tt>0x6f48cd30</tt>
|-
| LegL || <tt>0xeb4cc189</tt>
|-
| LegR || <tt>0x1143fcea</tt>
|-
| LFLeg_joint41 || <tt>0x88363db0</tt>
|-
| LFLeg_joint42 || <tt>0x113f6c0a</tt>
|-
| LFLeg_joint43 || <tt>0x66385c9c</tt>
|-
| LFLeg_joint46 || <tt>0x1652a813</tt>
|-
| LMLeg_joint41 || <tt>0xf9a499be</tt>
|-
| LMLeg_joint42 || <tt>0x60adc804</tt>
|-
| LMLeg_joint43 || <tt>0x17aaf892</tt>
|-
| LMLeg_joint45 || <tt>0xfec95da7</tt>
|-
| Lower_01 || <tt>0x0955c6d0</tt>
|-
| Lower_02 || <tt>0x905c976a</tt>
|-
| Lower_03 || <tt>0xe75ba7fc</tt>
|-
| Lower_04 || <tt>0x793f325f</tt>
|-
| Lower_05 || <tt>0x0e3802c9</tt>
|-
| Lower_06 || <tt>0x97315373</tt>
|-
| MiddleFingerTipL || <tt>0xc820b7d8</tt>
|-
| MiddleFingerTipR || <tt>0x322f8abb</tt>
|-
| Neck || <tt>0x8023796d</tt>
|-
| NoseLeft_joint18 || <tt>0xbc61b1ae</tt>
|-
| Object26 || <tt>0x29aadf6c</tt>
|-
| PinkyFingerTipL || <tt>0x33785489</tt>
|-
| PinkyFingerTipR || <tt>0xc97769ea</tt>
|-
| PinkyL01 || <tt>0x1319a094</tt>
|-
| PinkyL02 || <tt>0x8a10f12e</tt>
|-
| PinkyL03 || <tt>0xfd17c1b8</tt>
|-
| PinkyR01 || <tt>0x05a12eee</tt>
|-
| PinkyR02 || <tt>0x9ca87f54</tt>
|-
| PinkyR03 || <tt>0xebaf4fc2</tt>
|-
| Plane01 || <tt>0x7ea01827</tt>
|-
| Plane02 || <tt>0xe7a9499d</tt>
|-
| Plane03 || <tt>0x90ae790b</tt>
|-
| Plane04 || <tt>0x0ecaeca8</tt>
|-
| Plane05 || <tt>0x79cddc3e</tt>
|-
| RFLeg_joint41 || <tt>0xc6d797da</tt>
|-
| RFLeg_joint42 || <tt>0x5fdec660</tt>
|-
| RFLeg_joint43 || <tt>0x28d9f6f6</tt>
|-
| RFLeg_joint49 || <tt>0xc80c1fe8</tt>
|-
| RingFingerTipL || <tt>0x46372b47</tt>
|-
| RingFingerTipR || <tt>0xbc381624</tt>
|-
| RLLeg_joint042 || <tt>0x3c3468ef</tt>
|-
| RLLeg_joint043 || <tt>0x4b335879</tt>
|-
| RLLeg_joint044 || <tt>0xd557cdda</tt>
|-
| RLLeg_joint40 || <tt>0x5be74f2d</tt>
|-
| RLLeg_joint41 || <tt>0x2ce07fbb</tt>
|-
| RLLeg_joint42 || <tt>0xb5e92e01</tt>
|-
| RLLeg_joint43 || <tt>0xc2ee1e97</tt>
|-
| RLLeg_joint47 || <tt>0xc583da8e</tt>
|-
| RMLeg_joint41 || <tt>0xb74533d4</tt>
|-
| RMLeg_joint42 || <tt>0x2e4c626e</tt>
|-
| RMLeg_joint43 || <tt>0x594b52f8</tt>
|-
| RMLeg_joint48 || <tt>0xce998b70</tt>
|-
| Spine1Sale_joint || <tt>0x65038b0e</tt>
|-
| Spine2_joint || <tt>0x76b94270</tt>
|-
| Spine2Scale_joint || <tt>0xff91da65</tt>
|-
| Spine3_joint || <tt>0xd0ce49c4</tt>
|-
| Spine3Scale_joint || <tt>0x6434960a</tt>
|-
| SpineLower || <tt>0x04672713</tt>
|-
| SpineUp || <tt>0x127d657f</tt>
|-
| SpineUpper || <tt>0x64a283ac</tt>
|-
| Tail01 || <tt>0x0bf409bb</tt>
|-
| Tail02 || <tt>0x92fd5801</tt>
|-
| Tail1_joint || <tt>0x425a6dd1</tt>
|-
| Tail2_joint || <tt>0x73b2774c</tt>
|-
| Tail3_joint || <tt>0xd5c57cf8</tt>
|-
| Tail3Scale_joint || <tt>0xb632d0ba</tt>
|-
| Tail4_joint || <tt>0x10624276</tt>
|-
| Taile4Scale_joint || <tt>0x991f686c</tt>
|-
| TailScale_joint || <tt>0x0e8b6be9</tt>
|-
| thumb_L || <tt>0x9088b57e</tt>
|-
| thumb_R || <tt>0x6a87881d</tt>
|-
| ThumbTipL || <tt>0x704d64e8</tt>
|-
| ThumbTipR || <tt>0x8a42598b</tt>
|-
| ToeBackL || <tt>0x83d2d151</tt>
|-
| ToeBackR || <tt>0x79ddec32</tt>
|-
| ToeFrontL || <tt>0x56d43f69</tt>
|-
| ToeFrontR || <tt>0xacdb020a</tt>
|-
| ToeL || <tt>0x414059ad</tt>
|-
| ToeR || <tt>0xbb4f64ce</tt>
|-
| Upper_01 || <tt>0xe632304f</tt>
|-
| Upper_02 || <tt>0x7f3b61f5</tt>
|-
| Upper_03 || <tt>0x083c5163</tt>
|-
| Upper_04 || <tt>0x9658c4c0</tt>
|-
| Upper_05 || <tt>0xe15ff456</tt>
|-
| Upper_06 || <tt>0x7856a5ec</tt>
|-
| WristL || <tt>0x07fbf58a</tt>
|-
|}
</div></div>
 
The following are unconfirmed names for non-keybones that were likely used in the past but are no longer used in any models today.
* Geo_EyeglowR
* Leg02Middle_L
* Leg03Middle_L
* NoseRight_joint39
 
====Hierarchy requirement====
from animkits (some 8.x+9.x build):
* Full Body (-1 or 26 or 5)
** Upper Body (4)
*** Right Shoulder (2)
**** Right Arm (1)
***** Right Hand (8 or 9 or 10 or 11 or 12)
*** Left Shoulder (3)
**** Left Arm (0)
***** Left Hand (13 or 14 or 15 or 16 or 17)
*** Head (6)
**** Jaw (7)
*** Face Upper (193) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}
*** Face Lower (228) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}
*** Hair (191) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}
*** Beard (192) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}
** Wheel1…8 (27…34)
 
=Geometry and rendering=
==Vertices==
struct M2Vertex
{
  {{Template:Type|C3Vector}} pos;
  uint8 bone_weights[4];
  uint8 bone_indices[4];
  {{Template:Type|C3Vector}} normal;
  {{Template:Type|C2Vector}} tex_coords[2];  // two textures, depending on shader used
};
 
Models, too, use a Z-up coordinate systems, so in order to convert to Y-up, the X, Y, Z values become (X, -Z, Y).
 
-- Rour, the WoW vertex shaders all follow the same pattern, "Diffuse_XX_YY" (or sometimes XX, YY and Env). The particular vertex shader that is used chooses which set of texture coordinates to use. So Diffuse_T1 sends T1 texcoords to the fragment shader. Where Diffuse_T1_T2 sends both (for textures 0 and 1) but Diffuse_T1_T1 sends the same coords for both textures. Etc.
 
==Views (LOD)==
Skin profiles describe LOD views onto the model. They use all those lookup tables to be able to reference only parts of the lists while not being dynamically sized within the profile data.
 
{{Template:Sandbox/VersionRange|max_expansionlevel=2}} they were stored in the [[M2]] itself, {{Template:Sandbox/VersionRange|min_expansionlevel=3}} they have been moved to [[M2/.skin|.skin]] files. The offsets are relative to the file the skin profile header is defined in. There is one [[M2/.skin|.skin]] file per profile, each with a separate header, while in the inlined version, all headers are sequential. See the [[M2/.skin|.skin]] file page for formats of both versions.
 
==Render flags and blending modes==
struct M2Material
{
  uint16_t flags;
  uint16_t blending_mode; // apparently a bitfield
} materials[];
*'''Flags:'''
{| class="wikitable"
! width="70" | Flag !! width="1000" | Meaning
|-
| 0x01 || Unlit
|-
| 0x02 || Unfogged
|-
| 0x04 || Two-sided (no backface culling if set)
|-
| 0x08 || depthTest
|-
| 0x10 || depthWrite
|-
| 0x40 || shadow batch related ??? (seen in WoD) (seen in 31 models in Wrath)
|-
| 0x80 || shadow batch related ??? (seen in WoD) (seen in 2 models in Wrath : SpectralTiger.m2 and SpectralTigerEpic.m2)
|-
| 0x100 || (seen in 1 model in Wrath : HFjord_Fog_02.m2)
|-
| 0x200 || shadow batch related ??? (seen in WoD)
|-
| 0x400 || ??? (seen in WoD)
|-
| 0x800 || prevent alpha for custom elements. if set, use (fully) opaque or transparent. (litSphere, shadowMonk) (MoP+)
|}
 
*'''Blending mode'''
See the table at [[M2/Rendering#M2BLEND]] for a list of the current blend modes in the client.
 
===Second Texture Material Override Combo===
 
If this block is present (globalflags&8) the M2 will use multitexturing differently, the second texture will get the material to use from here (flags and blending mode) instead of current index material + 1, the Shader ID from the [[https://wowdev.wiki/M2/.skin#Texture_units| Texture Unit]] will tell what Second Texture Material to use (so, Shader ID will always be an even number), while materialIndex will still be used as the material for the first texture.
 
When multitexturing is applied, the second texture will get all the needed information from the first texture + 1, so, the texture to use will be the Index into Texture lookup table + 1, same for [[https://wowdev.wiki/M2#Texture_mapping_lookup_table| Texture Mapping]], TexAnim, etc.
 
According to wod, if the M2 Header has flag 0x08, instead of reading blend mode from M2 [[#Render_flags_and_blending_modes|RenderFlags]], blendMode is read from the raw blend maps referenced in header.
 
var flags = renderFlags[texUnit.renderFlags];
var blendMode = flags >> 16;
if ((header.GlobalModelFlags & 0x08) != 0 && texUnit.shader_id < mBlendMap.Length)
  blendMode = mBlendMap[texUnit.shader_id];
 
==Texture mapping lookup table==
{{Template:SectionBox/VersionRange|max_expansionlevel=4|max_exclusive=1|note=Still present but unused in Cataclysm}}
struct
{
  int16 unit; // -1, 0, or 1. see below
} tex_mapping_lookup_table[]; (alt. name: tex_unit_lookup_table, textureCoordCombos)
 
{| class="wikitable"
! width="70" | Value !! width="180" | Description
|-
| -1 || Environment mapping
|-
| 0 || First UVMap
|-
| 1 || Second UVMap
|}
 
Referenced in the [[https://wowdev.wiki/M2/.skin#Texture_units|.skin#Texture_units| Skin Texture Unit]], defines the mapping to be used for textures. Typically, when there's no multitexturing, the value is set to 0, indicating the use of the first UVMap. However, it can also be set to 1 or -1, representing the second UVMap or environment mapping, respectively. For models that utilize multitexturing, the table also specifies the mappings to be used by both textures. It functions in pairs, like the [[https://wowdev.wiki/M2#Texture_lookup_table| Texture Lookup Table]]. The first texture will use the mapping corresponding to the provided ID, while the second texture will use the mapping associated with the next ID in sequence.
 
One model is of special interest, Creature/KelThuzad/KelThuzad.m2, which is the only one that has an nTexUnits of 3, and has three texture units specified for some of its submeshes. Sure enough, two of those map to 0 and 1, and one maps to -1.
 
More confusion thanks to my favorite "weird" model, World/Generic/Gnome/Passive Doodads/GnomeMachine/GnomeSubwayGlass.m2, which is the translucent, environment mapped glass tunnel in the Deeprun Tram. It only has a single value in this block, -1, which is used for the single texture layer in both render operations in the model. This and the magic with rendering flags/blend modes make up the neat transparent-reflective glass effect, but confuse me even more about how envmapping and such is handled. (and where it seems to get the bluish color from - is it in the model (no color blocks in this particular model), the wmo, a solid background color, or simply the result of the blending used?)
 
As a side note, on my (dated) system WoW does every texture unit in a single pass.
 
==Colors and transparency==
===Colors===
struct M2Color
{
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> color; // vertex colors in rgb order
  [[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alpha; // 0 - transparent, 0x7FFF - opaque. Normaly NonInterp
} colors[];
 
This block is the M2 equivalent to the GEOA chunk in MDX files, it represents the vertex color and visibility animations for meshes. Referenced from the Texture Unit blocks in the [[M2/.skin|*.skin]]. If a texunit belonging to a submesh has a value of -1 then the submesh doesnot use this block. Contains a separate timeline for transparency values. If no animation is used, the given value is constant.
 
===Transparency===
struct M2TextureWeight
{
  [[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> weight;
} textureWeights[];
 
Specifies global transparency values in addition to the values given in the Color block. I assume these are multiplied together eventually.
====Transparency lookup table====
struct
{
  uint16_t transparency;
} transparency_lookup[];
Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.
 
==Textures==
*'''Textures are defined globally in a list''', additionally, a lookup table is given, referenced during rendering, to select textures.
struct M2Texture
{
  uint32_t type;          // see below
  uint32_t flags;        // see below
  M2Array<char> filename; // for non-hardcoded textures (type != 0), this still points to a zero-byte-only string.
} textures[];
 
Note that at least {{Template:Sandbox/PrettyVersion|expansionlevel=6|build=6.0.1.18179}} relies on both
 
* non-type-0 textures having a one-byte sized string that has a first byte of <tt>\0</tt>
* the <tt>filename</tt> to contain a zero terminating character as it is reading them into an uninitialized buffer and passes that buffer on without the size given.
 
Thus, <tt>filename</tt>'s size '''shall contain''' the zero byte, i.e. <tt>buffer size</tt> rather than <tt>string length</tt>.
 
Also note that this buffer is on the stack and sized the usual <tt>0x108</tt> bytes, so this is a n implicit texture filename length limit.


*'''nRibbonEmitters records of 0xDC bytes starting at ofsRibbonEmitters, followed by data referenced in these records.'''
Note that at least {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.1.5.40944}} expects M2Array filename to be zeroed in order to lookup BLP FileDataIDs from TXID chunk. Otherwise you get a green texture in the game.


These are called ribbon emitters in the strings buried in wow.exe. I guess these are some sort of ribbon-like visual effects similar to particles. In Warcraft 3 (of which the WoW engine is quite loosely based off of) there was also the possibility of ribbon-like effects in trails and flowing clothing. This is also how in WoW the feathers of the gryphons, hippogryphs, and bats flow in the wind as you rise and descend.
====Texture Types====
Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!) For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name, the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:
*DBFilesClient\[[CharSections.dbc]]
*DBFilesClient\[[CreatureDisplayInfo.dbc]]
*DBFilesClient\[[ItemDisplayInfo.dbc]]
*(possibly more)
{| class="wikitable"
! width="70" | Value !! width="800" | Meaning
|-
| 0 || - NONE - -- Texture given in filename
|-
| 1 || TEX_COMPONENT_SKIN -- Skin -- Body + clothes
|-
| 2 || TEX_COMPONENT_OBJECT_SKIN -- Object Skin -- Item, Capes ("Item\ObjectComponents\Cape\*.blp")
|-
| 3 || TEX_COMPONENT_WEAPON_BLADE -- Weapon Blade -- Used on several models but not used in the client as far as I see. Armor Reflect?
|-
| 4 || TEX_COMPONENT_WEAPON_HANDLE -- Weapon Handle
|-
| 5 || TEX_COMPONENT_ENVIRONMENT -- (OBSOLETE) Environment (Please remove from source art)
|-
| 6 || TEX_COMPONENT_CHAR_HAIR -- Character Hair
|-
| 7 || TEX_COMPONENT_CHAR_FACIAL_HAIR -- (OBSOLETE) Character Facial Hair (Please remove from source art)
|-
| 8 || TEX_COMPONENT_SKIN_EXTRA -- Skin Extra
|-
| 9 || TEX_COMPONENT_UI_SKIN -- UI Skin -- Used on inventory art M2s (1): inventoryartgeometry.m2 and inventoryartgeometryold.m2
|-
| 10 || TEX_COMPONENT_TAUREN_MANE -- Character Misc // (OBSOLETE) Tauren Mane (Please remove from source art)
|-
| 11 || TEX_COMPONENT_MONSTER_1 -- Monster Skin 1 -- Skin for creatures or gameobjects #1
|-
| 12 || TEX_COMPONENT_MONSTER_2 -- Monster Skin 2 -- Skin for creatures or gameobjects #2
|-
| 13 || TEX_COMPONENT_MONSTER_3 -- Monster Skin 3 -- Skin for creatures or gameobjects #3
|-
| 14 || TEX_COMPONENT_ITEM_ICON -- Item Icon -- Used on inventory art M2s (2): ui-button.m2 and forcedbackpackitem.m2 (CSimpleModel_ReplaceIconTexture("texture"))
|-
| 15 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Background Color
|-
| 16 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem Color
|-
| 17 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Border Color
|-
| 18 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem
|-
| 19 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Character Eyes
|-
| 20 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Character Jewelry // Accessory
|-
| 21 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Character Secondary Skin
|-
| 22 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Character Secondary Hair
|-
| 23 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Character Secondary Armor
|-
| 24 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}}
|-
| 25 || Seen in DracthyrDragon.m2 (10.0.0+)
|-
| 26 || Seen in DracthyrDragon.m2 (10.0.0+)
|}


Note: the models that contain ribbon emitters and are viewable in the game world are: wisps in BFD, Al'ar the Phoenix in Tempest Keep(and any other phoenix models), and energy trails in the COT (not the actual instance, but the entrance cave in Tanaris Desert). The only other models with ribbon emitters are spells and effects. Gryphons/etc don't have ribbon emitters, the feathers are part of their geometry.
====Flags====
{| class="wikitable"
! width="70" | Value !! width="200" | Meaning
|-
| 0x1 || Texture wrap X
|-
| 0x2 || Texture wrap Y
|-
| 0x1+0x2 || Texture wrap X+Texture wrap Y
|}


''What about the ribbons in the model of the undead ziggurat crystals in Stratholme?''
====Texture lookup table====
struct
{
  uint16_t texture;
} texture_lookup[];


The records have the following structure:
====Replacable texture lookup====
'''Offset Type Description'''
  struct
0x00 int32 ID? always -1
  {
0x04 int32 Bone ID
  uint16_t replacement;
0x08 float[3] position
  } texture_replacements[];
0x14 int32 Number of texture refs
0x18 int32 Offset to texture refs (ints)
0x1C int32 Number of ints #2
0x20 int32 Offset to list of ints #2
0x24 AnimationBlock (float, float, float) Color
0x40 AnimationBlock (short) Opacity
0x5C AnimationBlock (float) Height above
0x78 AnimationBlock (float) Height below
  0x94 float Something about length/lifespan?
  0x98 float Something about length/lifespan?
0x9C float Usually 0
  0xA0 short[2] (blending modes maybe?) (1,1)
0xA4 AnimationBlock (int) ? (always 0)
0xC0 AnimationBlock (int) ? (always 1)


Parameters from the MDL format that are probably in here somewhere: life span, emission rate, rows, cols ...?
A reverse lookup table for 'replaced' textures, mapping replacable ids to texture indices or -1. Only goes up to the maximum id used in the model.


==  Particle emitters ==
Its strange, that HARDCODED is in the list, as a model can have more than one of course. Its just the last one written to the file.


*'''nparticleEmitters records of 0x1F8 bytes starting at ofsParticleEmitters, followed by data referenced in these records.'''
=Effects=
==Texture Transforms==
*'''This block contains definitions for texture animations,''' for example, flowing water or lava in some models. The keyframe values are used in the texture transform matrix.


The records have the following structure:
  struct M2TextureTransform
  '''Offset Type Description'''
  {
  0x000 int32 id (always -1?)
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;
0x004 int32 flags
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation;    // rotation center is texture center (0.5, 0.5)
0x008 float[3] position
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scaling;
0x014 int16 Bone ID
  } textureTransforms[];
0x016 int16 Texture ID
0x018 uint32 Number of ints referenced #1
0x01C uint32 Offset to list of ints #1
0x020 uint32 Number of ints referenced #2
0x024 uint32 Offset to list of ints #2
0x028 int16 Blending mode
0x02A int16 Emitter type
0x02C int16 Particle type
0x02E int16 Texture tile rotation (-1,0,1)
0x030 int16 Rows on texture
0x032 int16 Columns on texture
0x034 AnimationBlock (float) [10] Parameters
0x14C float[36] unknown float values - more parameters?
0x14C float Midpoint in lifespan? (0 to 1)
0x150 uint32[3] ARGB colors (start, mid, end)
0x15C float[3] Particle sizes (start, mid, end)
0x168 short[10] Indices into the tiles on the texture?
0x17C float[3] Unknown
0x188 float[3] Something about particle scaling? Hm.
0x194 float Slowdown
0x198 float Particle rotation
  0x19C float[10] Unknown (usually all 0)
0x1C4 float[6] Unknown, usually (2.5, 0.7, 7, 0.9, 0, 0)
0x1DC AnimationBlock (int) unknown
About slowdown: for nonzero values, instead of travelling linearly the particles seem to slow down sooner. I can't work out the exact function but for a value of, say, 10, the particles pretty much stay in place. Not the same effect as gravity, though. Update: thanks to nsz for the formula. Speed is multiplied by exp( -slowdown * t )


About particle rotation: 0 for none, 1 to rotate the particle 360 degrees throughout its lifetime.
Seems like UV rotation in some models are made against (0.5, 0.5) point instead of (0, 0). At least it's the case for world\goober\g_scourgerunecirclecrystal.m2


The ten animatable particle parameters are:
So to get the proper UV rotation it would be necessary apply rotation this way:
'''Number Description'''
0 Emission speed
1 Speed variation (range: 0 to 1)
2 Vertical Range (range: 0 to pi)
3 Horizontal Range (0 to 2*pi)
4 Gravity
5 Lifespan
6 Emission rate
7 Emission area length
8 Emission area width
9 Gravity? (much stronger)


Emitter types:
* Translate UV anim matrix to point (0.5, 0.5)
'''Value Description'''
* Apply rotation mat from quaternion
1 Plane (rectangle)
* UV anim matrix to point (-0.5, -0.5)
2 Sphere
3 (Spline? can't be bothered to find one)


Particle types:
====Texture Transforms lookup table ====
  '''Value Description'''
  struct
  0 "normal" particle
  {
1 large quad from the particle's origin to its position (used in Moonwell water effects)
  uint16_t anim_texture_id; // -1 for static
  2 seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)
  } anim_texture_lookup[];


Flags:
== Ribbon emitters ==
  '''Value Description'''
struct M2Ribbon
0x1000 do not billboard (used for some water wake ripple effects)
{
  uint32_t ribbonId;                  // Always (as I have seen): -1.
  uint32_t boneIndex;                // A bone to attach to.
  {{Template:Type|C3Vector}} position;                // And a position, relative to that bone.
  M2Array<uint16_t> textureIndices;  // into [[#Textures|textures]]
  M2Array<uint16_t> materialIndices; // into [[#Render_flags_and_blending_modes|materials]]
  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> colorTrack;      // An RGB multiple for the material.[[#References|[1]]]
  [[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alphaTrack;      // And an alpha value in a short, where: 0 - transparent, 0x7FFF - opaque.
  [[#Standard_animation_block|M2Track]]<float> heightAboveTrack;    // Above and Below – These fields define the width of a ribbon in units based on their offset from the origin.[[#References|[1]]]
  [[#Standard_animation_block|M2Track]]<float> heightBelowTrack;    // do not set to same!
  float edgesPerSecond;              // this defines how smooth the ribbon is. A low value may produce a lot of edges.
                                      // Edges/Sec – The number of quads generated.[[#References|[1]]]
  float edgeLifetime;                // the length aka Lifespan. in seconds
                                      // Time in seconds that the quads stay around after being generated.[[#References|[1]]]
  float gravity;                      // use arcsin(val) to get the emission angle in degree
                                      // Can be positive or negative. Will cause the ribbon to sink or rise in the z axis over time.[[#References|[1]]]
  uint16_t textureRows;              // tiles in texture
  uint16_t textureCols;              // Texture Rows and Cols – Allows an animating texture similar to BlizParticle. Set the number of rows and columns equal to the texture.[[#References|[1]]]
  [[#Standard_animation_block|M2Track]]<uint16_t> texSlotTrack;    // Pick the index number of rows and columns, and animate this number to get a cycle.[[#References|[1]]]
  [[#Standard_animation_block|M2Track]]<uchar> visibilityTrack;
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}                          // TODO: verify version
  int16_t priorityPlane;
  int8_t RibbonColorIndex;
  int8_t textureTransformLookupIndex; //Index into m2data.header.textureTransformCombos. Applied only if m2data.header.global_flags.flag_unk_0x20000 flag is set
#endif
} ribbons[];
Some models that contain ribbon emitters and are viewable in the game world are: Wisps in BFD, Al'ar the Phoenix in Tempest Keep and any other phoenix models and the energy trails in the COT (not the actual instance, but the entrance cave in Tanaris Desert). Other models with ribbon emitters are spells and effects.


''Parameters from the MDL format that are probably in here somewhere: emission rate, rows, cols ...?''


=Miscellaneous=
== Particle emitters ==
'''This is partly wrong as hell!''' Do not rely on this block, at all. It might even be wrong for WotLK.
===M2ParticleOld===
struct M2ParticleOld {
/*0x00*/  uint32 particleId;                        // Always (as I have seen): -1.
/*0x04*/  uint32 flags;                            // See Below
/*0x08*/  {{Template:Type|C3Vector}} Position;                      // The position. Relative to the following bone.
/*0x14*/  uint16 bone;                              // The [[#Bones|bone]] its attached to.
/*0x16*/  union
  {
    uint16 texture;                        // And the [[#Texture_definitions|textures]] that are used.
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}
    struct                                  // For multi-textured particles actually three ids
    {
      uint16_t texture_0 : 5;
      uint16_t texture_1 : 5;
      uint16_t texture_2 : 5;
      uint16_t : 1;
    };
#endif
  };
/*0x18*/  M2Array<char> geometry_model_filename;    // if given, this emitter spawns models
/*0x20*/  M2Array<char> recursion_model_filename;  // if given, this emitter is an '''alias''' for the (maximum 4) emitters of the given model
#if >= 262 (late Burning Crusade)
/*0x28*/  uint8 blendingType;                      // A blending type for the particle. See Below
/*0x29*/  uint8 emitterType;                        // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone
/*0x2a*/  uint16 particleColorIndex;                // This one is used for [[ParticleColor.dbc]]. See below.
#else
/*0x28*/  uint16 blendingType;                      // A blending type for the particle. See Below
/*0x2a*/  uint16 emitterType;                      // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone
#endif
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}
/*0x2c*/  {{Template:Type|fixed_point}}<uint8_t, 2, 5> multiTextureParamX[2];
#else
/*0x2c*/  uint8 particleType;                      // Found below.
/*0x2d*/  uint8 headorTail;                        // 0 - Head, 1 - Tail, 2 - Both
                                            // Head - The particle is a billboarded square quad;[[#References|[1]]]
                                            // Tail – A tail particle is billboarded along the axis of motion and stretches in length based on speed;[[#References|[1]]]
                                            // Both – Draws both heads and tails;[[#References|[1]]]
#endif
  uint16 textureTileRotation;              // Rotation for the texture tile. (Values: -1,0,1) -- priorityPlane
  uint16 textureDimensions_rows;            // for tiled textures
  uint16 textureDimensions_columns;
  [[#Standard_animation_block|M2Track]]<float> emissionSpeed;            // Base velocity at which particles are emitted.
  [[#Standard_animation_block|M2Track]]<float> speedVariation;            // Random variation in particle emission speed. (range: 0 to 1)
  [[#Standard_animation_block|M2Track]]<float> verticalRange;            // longitude; Drifting away vertically. (range: 0 to pi) For plane generators, this is the maximum polar angle of the initial velocity;
                                            // 0 makes the velocity straight up (+z). For sphere generators, this is the maximum elevation of the initial position;
                                            // 0 makes the initial position entirely in the x-y plane (z=0).
  [[#Standard_animation_block|M2Track]]<float> horizontalRange;          // latitude; They can do it horizontally too! (range: 0 to 2*pi) For plane generators, this is the maximum azimuth angle of the initial velocity;
                                            // 0 makes the velocity have no sideways (y-axis) component.
                                            // For sphere generators, this is the maximum azimuth angle of the initial position.
  [[#Standard_animation_block|M2Track]]<float> gravity;                  // Not necessarily a float; [[#Compressed Particle Gravity|see below]].
  [[#Standard_animation_block|M2Track]]<float> lifespan;                  // Number of seconds each particle continues to be drawn after its creation.[[#References|[1]]]
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
  float lifespanVary;                      // An individual particle's lifespan is added to by <code>lifespanVary * random(-1, 1)</code>
#endif
  [[#Standard_animation_block|M2Track]]<float> emissionRate;
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
  float emissionRateVary;                  // This adds to the base emissionRate value the same way as lifespanVary. The random value is different every update.
#endif
  [[#Standard_animation_block|M2Track]]<float> emissionAreaLength;        // For plane generators, this is the width of the plane in the x-axis.
                                            // For sphere generators, this is the minimum radius.
  [[#Standard_animation_block|M2Track]]<float> emissionAreaWidth;        // For plane generators, this is the width of the plane in the y-axis.
                                            // For sphere generators, this is the maximum radius.
  [[#Standard_animation_block|M2Track]]<float> zSource;                  // When greater than 0, the initial velocity of the particle is <code>(particle.position - C3Vector(0, 0, zSource)).Normalize()</code>
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
  [[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C3Vector}}> colorTrack;            // Most likely they all have 3 timestamps for {start, middle, end}.
  [[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|fixed16}}> alphaTrack;
  [[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C2Vector}}> scaleTrack;
  {{Template:Type|C2Vector}} scaleVary;                      // A percentage amount to randomly vary the scale of each particle
  [[#The_Fake-AnimationBlock|FBlock]]<uint16> headCellTrack;            // Some kind of intensity values seen: 0,16,17,32 (if set to different it will have high intensity)
  [[#The_Fake-AnimationBlock|FBlock]]<uint16> tailCellTrack;
#else
  float midPoint;                          // middleTime; Middle point in lifespan (0 to 1).
                                            // Time (parametric) Middle – The relative time of the middle key to the life.[[#References|[1]]]
  {{Type|CImVector}}[3] colorValues;                // Color, Start/Middle/End – Three values of BGRA Multiply for the particle.[[#References|[1]]]
  float[3] scaleValues;                    // Scale, Start/Middle/End – Scale of the particle in units across.[[#References|[1]]]
  uint16[3] lifespanUVAnim;                // Lifespan Head UV Anim – Starting, middle and ending cell number for animating texture sequence of the first half of the Head particle’s life. Repeat will cause this animating texture to cycle.[[#References|[1]]]
  uint16[3] decayUVAnim;                    // Decay Head UV Anim - Starting, middle and ending cell number for animating texture sequence of the second half of the Head particle’s life. Repeat will cause this animating texture to cycle.[[#References|[1]]]
  int16[2] tailUVAnim;                      // Lifespan Tail UV Anim - Starting and ending cell number for animating texture sequence of the first half of the Tail particle’s life. Repeat will cause this animating texture to cycle.[[#References|[1]]]
  int16[2] tailDecayUVAnim;                // Decay Tail UV Anim - Starting and ending cell number for animating texture sequence of the second half of the Tail particle’s life. Repeat will cause this animating texture to cycle.[[#References|[1]]]
#endif
  float tailLength;                        // A multiplier to the calculated tail particle length.[[#References|[1]]]
  float twinkleSpeed;                      // twinkleFPS; has something to do with the spread
  float twinklePercent;                    // same mechanic as MDL twinkleOnOff but non-binary in 0.11.0
  {{Template:Type|CRange}} twinkleScale;                    // min, max
  float burstMultiplier;                    // ivelScale; requires (flags & 0x40)
  float drag;                              // For a non-zero values, instead of travelling linearly the particles seem to slow down sooner. Speed is multiplied by exp( -drag * t ).
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
  float baseSpin;                          // Initial rotation of the particle quad
  float baseSpinVary;
  float spin;                              // Rotation of the particle quad per second
  float spinVary;
#else
  float spin;                              // 0.0 for none, 1.0 to rotate the particle 360 degrees throughout its lifetime.
#endif
  [[#M2Box|M2Box]] tumble;
  {{Template:Type|C3Vector}} windVector;
  float windTime;
  float followSpeed1;
  float followScale1;
  float followSpeed2;
  float followScale2;
  M2Array<C3Vector> splinePoints;                                  // Set only for spline praticle emitter. Contains array of points for spline
  [[#Standard_animation_block|M2Track]]<uchar> enabledIn;                // (boolean) Appears to be used sparely now, probably there's a flag that links particles to animation sets where they are enabled.
} particles[];


==  Bounding volumes ==
Spin can be a float value greater or less one. Results look better if use it as a "phase shift": particle_rotate = randfloat(-sys->rotation * pi, sys->rotation * pi); --Igor


*'''For some models a simplified bounding volume is given.''' This is probably used for collision detection?
===M2Particle (Cata+)===
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}
* Cata+ has multi texture support
using fp_6_9 = {{Template:Type|fixed_point}}<uint16_t, 6, 9>;
struct vector_2fp_6_9 { fp_6_9 x; fp_6_9 y; };
struct M2Particle
{
  M2ParticleOld old;
  vector_2fp_6_9 multiTextureParam0[2];
  vector_2fp_6_9 multiTextureParam1[2];
} particles[];


The nBoundingVertices vertices used by the bounding volume's faces start at '''ofsBoundingVertices'''. Each vertex is given by 3 floats.
In addition to these two parameters, <code>ParticleType</code> and <code>HeadOrTail</code> got reused (as in replaced at their current position) as <code>multiTextureParamX[2]</code> where all arrays are one entry per additional texture.


The faces start at '''ofsBoundingTriangles'''. The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles. Each triangle is given by 3 16-bit integers that index the bounding vertices list.
I don't know if the previous meaning of the two parameters still exists, got moved, or was just never used to begin with. ParticleType appears to be implicit by having <code>flags & 0x10100000</code> (→ multi texture), a model (→ model) or neither (→ default).--[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 23:47, 29 October 2015 (UTC)


Each face also has a corresponding normal vector (?), these start at '''ofsBoundingNormals'''. Therefore, it should be true that nBoundingNormals * 3 = nBoundingTriangles.
===Particle Flags===
{| class="wikitable"
! width="70" | Value !! width="600" | Description
|-
| 0x1 || Particles are affected by lighting;
|-
| 0x2 ||
|-
| 0x4 || On emission, particle orientation is affected by player orientation
|-
| 0x8 || Particles travel "up" in world space, rather than model.
|-
| 0x10 || Do not Trail
|-
| 0x20 || Unlightning
|-
| 0x40 || Use Burst Multiplier
|-
| 0x80 || Particles in Model Space (Causes animation of the particle emitter to be carried over to the particles.[[#References|[1]]])
|-
| 0x100 ||
|-
| 0x200 || spawn position randomized in some way?
|-
| 0x400 || STYLE: Pinned Particles, their quad enlarges from their creation position to where they expand.
|-
| 0x800 ||
|-
| 0x1000 || XYQuad Particles. They align to XY axis facing Z axis direction. (Causes the particles to be a type of tail that orients to the XY grid.[[#References|[1]]])
|-
| 0x2000 || clamp to ground; call CParticleEmitter2::ProjectParticle
|-
| 0x4000 ||
|-
| 0x8000 ||
|-
| 0x10000 || ChooseRandomTexture
|-
| 0x20000 || STYLE: "Outward" particles, most emitters have this and their particles move away from the origin, when they don't the particles start at origin+(speed*life) and move towards the origin.
|-
| 0x40000 || STYLE: unknown. In a large proportion of particles this seems to be simply the opposite of the above flag, but in some (e.g. voidgod.m2 or wingedlionmount.m2) both flags are true.
|-
| 0x80000 || If set, ScaleVary affects x and y independently; if not set, ScaleVary.x affects x and y uniformly, and ScaleVary.y is not used.
|-
| 0x200000 || Random FlipBookStart
|-
| 0x400000 || Ignores Distance (or 0x4000000?!, CMapObjDef::SetDoodadEmittersIgnoresDistance has this one)
|-
| 0x800000 || gravity values are compressed vectors instead of z-axis values (see Compressed Particle Gravity below)
|-
| 0x1000000 || bone generator = bone, not joint
|-
| 0x4000000 || do not throttle emission rate based on distance
|-
| 0x10000000 || Particle uses multi-texturing (could be one of the other WoD-specific flags), see multi-textured section.
|}


==  Lights ==
--[[User:Barncastle|Barncastle]] ([[User talk:Barncastle|talk]]) A comparison of 0.10.0's MDX files to 0.11.0's and 1.12.1's M2 files indicates that the [[MDX#Flags|MDX PRE2 flags]] (≥0x8000) were probably unchanged during the switch from MDL to M2, albeit separated into their own enum.


*'''nLights records of 0xD4 bytes starting at ofsLights, followed by data referenced in these records.'''
===ParticleColorIndex===
This is used in conjunction with [[ParticleColor.dbc]] to alter the default colour of particles. If the particle colour is not meant to be changed then its ParticleColorIndex will have a value of zero. If the particle colour may be changed then the value will be 11, 12 or 13, indicating whether the first, second or third Start, Mid and End colours are to be used, respectively. The row of [[ParticleColor.dbc]] to be used is determined its ID value, which should correspond to the ParticleColorID value supplied by [[CreatureDisplayInfo.dbc]] or [[ItemDisplayInfo.dbc]] for creatures or items.
===Particle types===
{| class="wikitable"
! width="70" | Value !! width="500" | Description
|-
| 0 || "normal" particle
|-
| 1 || large quad from the particle's origin to its position (used in Moonwell water effects)
|-
| 2 || seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)
|}
''ParticleType is always 0 and, maybe, now (Flags & 0x40000) != 0 means "particles from origin to position". --Igor''
''Checked and verified --BlinkHawk''
===Particle Blendings===
{| class="wikitable"
! width="70" | Value !! width="500" | Description
|-
| 0 || glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST);
|-
| 1 || glBlendFunc(GL_SRC_COLOR, GL_ONE);
|-
| 2 || glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|-
| 3 || glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST);
|-
| 4 || glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|}
from Modelviewer source
-- Rour, some WoD particle effects are using blend mode 0x7 here.
===The Fake-AnimationBlock===
*Its pretty much like the real one but without the "header".
{| class="wikitable"
! width="70" | Offset !! width="90" | Type !! width="120" | Name !! width="500" | Description
|-
| 0x000 || uint32 || nTimestamps || The number of timestamps.
|-
| 0x004 || uint32 || ofsTimestamps || And the offset to them. The timestamps are shorts! (?)
|-
| 0x008 || uint32 || nKeys || The same number again. This time its the number of Keys / Values.
|-
| 0x00C || uint32 || ofsKeys || And their offset.
|}
But they're unable to change between different animations, so they directly point to the data.


The records have the following structure:
===Compressed Particle Gravity===
'''Offset Type                     Description'''
Key values in the gravity track are decompressed at load time from a 4-byte value to a C3Vector.
0x00 uint16                         Type
0x02 uint16                         Bone ID
0x04 float[3]                         Coordinates
0x10 AnimationBlock (float, float, float) Ambient color
0x2C AnimationBlock (float) Ambient intensity
0x48 AnimationBlock (float, float, float) Diffuse color
0x64 AnimationBlock (float) Diffuse intensity
0x80 AnimationBlock (float) Attenuation start?
0x9C AnimationBlock (float) Attenuation end?
0xB8 AnimationBlock (int) usually 1


Some light types:
<nowiki>
'''Value Description'''
struct CompressedParticleGravity {
0 Directional
  int8_t x, y;
1 Point light
  int16_t z;
};
for (/* each 4-byte value in the particle gravity track */) {
  float *pValue;
  C3Vector *pDst;
  if (particle.flags & 0x800000) {
    // interpret the 4 bytes at pValue as CompressedParticleGravity:
    CompressedParticleGravity v = new (pValue) CompressedParticleGravity();
    C3Vector dir = C3Vector(v.x, v.y, 0) * (1.0f / 128.0f);
    float z = sqrtf(1.0f - dir.Dot(dir));
    float mag = v.z * 0.04238648f;
    if (mag < 0) {
      z = -z;
      mag = -mag;
    }
    dir.z = z;
    dir *= mag;
    *pDst = dir;
  } else {
    *pDst = C3Vector(0, 0, -(*pValue));
  }
}</nowiki>


==  Cameras ==


*'''These blocks seem to be present in the "flyby" camera models which completely lack geometry,''' and the main menu backdrop models which are supposed to have a fixed camera. Additionally, characters and monsters also have this block.
===M2Box===
struct M2Box {
  {{Template:Type|C3Vector}} ModelRotationSpeedMin;
  {{Template:Type|C3Vector}} ModelRotationSpeedMax;
}


nCameras records of 0x7C bytes starting at ofsCameras, followed by data referenced in these records.
=Miscellaneous=
'''Offset Type                           Description'''
==Name==
0x00 int32                         ID or maybe a parent bone? usually -1
  char name[];
0x04 float                         FOV
0x08 float                         Far clipping plane
  0x0C float                         Near clipping plane
0x10 AnimationBlock (float, float, float) Translation for position?
0x2C float[3]                         Position?
0x38 AnimationBlock (float, float, float) Translation for target?
0x54 float[3]                         Target?
0x60 AnimationBlock (float) Rotation or roll? (usually 0 or 2*pi)


For static cameras the animatable data items are all 0. The FOV is not in radians, but some weird scale... Multiplying by about 35 seems to give correct-ish results in degrees.
Informative name used for debugging purposes. Not used in retail clients.


The reason that non-mainmenu and non-geometry M2s have cameras (atleast in Warcraft 3) was so you could see the unit's portrait. In WoW, these models have cameras so that when you enter the Character menu (press "C") you see your character regardless of what model you currently have. Also, I believe that one of the main menu models has two cameras in it (I believe it's the orc one...).-DG
==Bounding volumes==
These blocks give a simplified bounding volume for the model. Characters and creatures have just a simple box.
===Vertices===
This block defines the possible points used for the model. They are referenced in the triangles block later.
struct
{
  {{Template:Type|C3Vector}} position;
} bounding_vertices[];


Oh, that's right. Neat. I added menu backdrops using these cameras. Obviously it's still missing particle emitters, but that can be fixed eventually... - Z.
===Triangles===
The number in the header tells us how many uint16s there are, not how many triangles. To use this better, you should group three of them into an array. The nBoundingTriangles/3 indices will tell you which vertices are used for the triangle then.  


== Camera lookup table ==
  struct
{
  uint16_t index; // three entries pointing to vertices per triangle
} bounding_triangles[];


*'''Lookup table for cameras? It seems to be persent only if a camera is present too.'''
The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles.


nCameraLookup 16-bit integers starting at ofsCameraLookup. (values: 0..nCameras-1)
===Normals===
This one defines a normal per triangle. The vectors are normalized, but Blizzard seems to have some problems getting a simple vector normalized leading in several 0,0,0.999999999 ones. Whatever.


= Attachments =
As each vertex has a corresponding normal vector, it should be true that nBoundingNormals = nBoundingTriangles / 3.
== Block 1 ==
struct
{
  {{Template:Type|C3Vector}} normal;
} bounding_normals[];


*'''nAttachments records of 0x50 bytes starting at ofsAttachments, followed by data referenced in these records.'''
== Lights ==
  '''Offset Type Description'''
struct M2Light
  0x00 uint32 id (position of the record in block P)
{
  0x04 uint32 bone
/*0x00*/  uint16_t type;                      // Types are listed below.
  0x08 float[3] position
  /*0x02*/  int16_t bone;                      // -1 if not attached to a bone
  0x14 AnimationBlock (int) Data
  /*0x04*/  {{Template:Type|C3Vector}} position;                // relative to bone, if given
  0x40 float[3] Unknown Floats (rot maybe?)
  /*0x10*/  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> ambient_color;
  0x4C AnimationBlock (int) Data
  /*0x24*/  [[#Standard_animation_block|M2Track]]<float> ambient_intensity;  // defaults to 1.0
--[[User:Xayo|Xayo]] 02:33, 21 August 2008 (CEST)
/*0x38*/  [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> diffuse_color;
  /*0x4C*/  [[#Standard_animation_block|M2Track]]<float> diffuse_intensity;  // defaults to 1.0
  /*0x60*/  [[#Standard_animation_block|M2Track]]<float> attenuation_start;
/*0x74*/  [[#Standard_animation_block|M2Track]]<float> attenuation_end;
  /*0x88*/  [[#Standard_animation_block|M2Track]]<uint8_t> visibility;        // enabled?
/*0x9C*/
} lights[];


Much like Block 2 this one also specifies a bunch of locations on the body - hands, shoulders, head, back, knees etc. Maybe this one is actually used to put items on a character. This seems very likely as this block also contains positions for sheathed weapons, a shield, etc.
Two light types:
{| class="wikitable"
! width="70" | Value !! width="200" | Description
|-
| 0 || Directional
|-
| 1 || Point light
|}


Here's the list of position slots (by ID, or index in block P) for character models:
Directional light type is not used (at least in 3.3.5) outside login screen, and doesn't seem to be taken into account in world.
'''ID Description ID Description ID Description'''
0 Left wrist / Mountpoint 12 Back 24 -
1 Right palm 13 - 25 -
2 Left palm 14 - 26 Right back sheath
3 Right elbow 15 Bust 27 Left back sheath
4 Left elbow 16 Bust 28 Middle back sheath
5 Right shoulder 17 Face 29 Belly
6 Left shoulder 18 Above character 30 Left back
7 Right knee 19 Ground 31 Right back
8 Left knee 20 Top of head 32 Left hip sheath
9 - 21 Left palm 33 Right hip sheath
10 - 22 Right palm 34 Bust
11 Helmet 23 - 35 Right palm


'''ID 0 is ''left wrist / shield'' for mobs and ''"mountpoint"'' for mounts.'''
In burning crusade (and maybe more versions) the glues models hardcode constant attenuation to 0, linear attenuation to 0.7 and quadratic attenuation to 0.03


For weapons, usually 5 of these points are present, which correspond to the 5 columns in [[ItemVisuals.dbc]], which in turn has 5 models from [[ItemVisualEffects.dbc]]. This is for the weapon glowy effects and such. The effect ID is the last column in [[ItemDisplayInfo.dbc]].
==Cameras==
These blocks are present in the "flyby" camera models which completely lack geometry and the main menu backdrop models which are supposed to have a fixed camera. Additionally, characters and monsters also have this block. The reason that non-mainmenu and non-geometry M2s have cameras was is you can see the unit's portrait and the character info tab.


==Attachment Lookup==
struct M2Camera
{
  uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table.
#if {{Template:Sandbox/VersionRange|max_expansionlevel=4|max_exclusive=1}}
  float fov; // Diagonal FOV in radians. See below for conversion.
#endif
  float far_clip;
  float near_clip;
  [[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> positions; // How the camera's position moves. Should be 3*3 floats.
  {{Template:Type|C3Vector}} position_base;
  [[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> target_position; // How the target moves. Should be 3*3 floats.
  {{Template:Type|C3Vector}} target_position_base;
  [[#Standard_animation_block|M2Track]]<M2SplineKey<float>> roll; // The camera can have some roll-effect. Its 0 to 2*Pi.
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}
  [[#Standard_animation_block|M2Track]]<M2SplineKey<float>> FoV; //Diagonal FOV in radians. See below for conversion.
#endif
} cameras[];


*'''nAttachLookup 16-bit integers starting at ofsAttachLookup. (values: -1, 0 to nO-1)'''
===Camera field of view===


Lookup table for whatever is in block 1, I guess :)
The <tt>fov</tt> included in <tt>M2Camera</tt> is a diagonal field of view (in radians). The client converts it to a vertical field of view at runtime using the following formula:


==Block 2==
<syntaxhighlight lang="cpp">
float vfov = dfov / sqrt(1.0 + pow(aspect, 2.0));
</syntaxhighlight>


*'''nAttachments_2 records of 0x24 bytes starting at ofsAttachments_2, followed by data referenced in these records.'''
The aspect ratio used is determined by the rect being presented on (eg. the game window).


This might be definitions for weapon attachment slots or something like that... Mostly present on characters, creatures and items.
Note that this formula makes an assumption that the screen being projected to is planar (ie a monitor).
'''Offset Type Description'''
0x00 char[4] some kind of ID, starts with '$'
0x04 uint32 database ID?
0x08 uint32 bone ID
0x0C float[3] floats (coordinates?)
0x18 AnimBlock(int) AnimationBlock for Something unknown
--[[User:Xayo|Xayo]] 02:56, 21 August 2008 (CEST)


The records seem to contain a truncated animation block. The int values look like timestamps, and there are no data values. The ranges reference more integers than are given in the list (there's always 1 less item). Weird.
===Camera lookup table===
This block lists the different cameras existing in the model. The index in the array is also the type. CameraLookupTable[1] is always the character tab camera.


Each record specifies a transformation matrix for attaching another model to a certain point. By translating to the attachment position and then applying the transform matrix of the parent bone, the other model at its default origin will snap right into place. This is how weapons are affixed to the hands, helmets and shoulder armor attached, and spell effects are also positioned this way.
"-1" type cameras are not referenced.


Some position identifiers:
If nCameras >= 1, then nCameraLookup will be >= 1 regardless of whether any camera will be actually referenced in it. See interface/glues/models/ui_mainmenu_warlords/ui_mainmenu_warlords.m2. A valid block thus may be -1s only. This appears to be an exporter-quirk rather than a requirement.
'''ID Description'''
$TRD Crotch
$CCH Bust
$BTH In front of head
$CHD Head
$SHL, $SHR Left/right shoulder
$CSL, $CSR Left/right hand
$BWP, $BWR Right hand (for weapons maybe?)


Some unknows identifiers:
  struct
'''ID Found at'''
  {
$CST Frostwurm & Dragon
  uint16_t camera;
$CAH Frostwurm & Dragon
  } camera_lookup[];
$CPP Frostwurm & Dragon
$CSS Frostwurm & Dragon
$HIT Frostwurm & Dragon
$DTH Frostwurm & Dragon
$FSD Frostwurm & Dragon
  $WNG Frostwurm & Dragon
  $FL0, $FL1 Frostwurm & Dragon
$FR0, $FR1 Frostwurm & Dragon
$RL0, $RL1 Frostwurm & Dragon
  $RR0, $RR1 Frostwurm & Dragon


The rest are either copies of the crotch position, or down on the floor. I suppose these are used to position spell effects (like a levelup flash or something) and damage effects.
== Attachments ==
This block specifies a bunch of locations on the body - hands, shoulders, head, back, knees etc. It is used to put items on a character. This seems very likely as this block also contains positions for sheathed weapons, a shield, etc.
struct M2Attachment
{
  uint32_t id;                      // Referenced in the [[#Attachment_Lookup|lookup-block]] below.
  uint16_t bone;                    // attachment base
  uint16_t unknown;                // see BogBeast.m2 in vanilla for a model having values here
  {{Template:Type|C3Vector}} position;              // relative to bone; Often this value is the same as bone's pivot point
  [[#Standard_animation_block|M2Track]]<uchar> animate_attached;  // whether or not the attached model is animated when this model is. only a bool is used. default is true.
} attachments[];


= Deleted Blocks =
Meaning depends on type of model. The following are for creatures/characters mainly:


== Block D ==
{| class="wikitable"
! width="50" | ID !! width="250" | Description
! width="50" | ID !! width="150" | Description
! width="50" | ID !! width="150" | Description
! width="50" | ID !! width="150" | Description
! width="50" | ID !! width="200" | Description
|-
| 0 || Shield / MountMain / ItemVisual0
| 12 || Back
| 24 || Special2
| 36 || Bullet (version: somewhen after alpha)
| 48 || RightFoot
|-
| 1 || HandRight / ItemVisual1
| 13 || ShoulderFlapRight
| 25 || Special3
| 37 || SpellHandOmni (version: somewhen after alpha)
| 49 || ShieldNoGlove
|-
| 2 || HandLeft / ItemVisual2
| 14 || ShoulderFlapLeft
| 26 || SheathMainHand
| 38 || SpellHandDirected (version: somewhen after alpha)
| 50 || SpineLow
|-
| 3 || ElbowRight / ItemVisual3
| 15 || ChestBloodFront
| 27 || SheathOffHand
| 39 || VehicleSeat1 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 51 || AlteredShoulderR
|-
| 4 || ElbowLeft / ItemVisual4
| 16 || ChestBloodBack
| 28 || SheathShield
| 40 || VehicleSeat2 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 52 || AlteredShoulderL
|-
| 5 || ShoulderRight
| 17 || Breath
| 29 || PlayerNameMounted
| 41 || VehicleSeat3 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 53 || BeltBuckle {{Template:Sandbox/VersionRange|min_expansionlevel=5}}
|-
| 6 || ShoulderLeft
| 18 || PlayerName
| 30 || LargeWeaponLeft
| 42 || VehicleSeat4 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 54 || SheathCrossbow
|-
| 7 || KneeRight
| 19 || Base
| 31 || LargeWeaponRight
| 43 || VehicleSeat5 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 55 || HeadTop {{Template:Sandbox/VersionRange|min_expansionlevel=7}}
|-
| 8 || KneeLeft
| 20 || Head
| 32 || HipWeaponLeft
| 44 || VehicleSeat6 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 56 || VirtualSpellDirected {{Template:Sandbox/VersionRange|min_expansionlevel=8}}
|-
| 9 || HipRight
| 21 || SpellLeftHand
| 33 || HipWeaponRight
| 45 || VehicleSeat7 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 57 || Backpack {{Template:Sandbox/VersionRange|min_expansionlevel=8}}
|-
| 10 || HipLeft
| 22 || SpellRightHand
| 34 || Chest
| 46 || VehicleSeat8 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}
| 60 || Unknown {{Template:Sandbox/VersionRange|min_expansionlevel=8}}
|-
| 11 || Helm
| 23 || Special1
| 35 || HandArrow
| 47 || LeftFoot
|}
For weapons, usually 5 of these points are present, which correspond to the 5 columns in [[ItemVisuals.dbc]], which in turn has 5 models from [[ItemVisualEffects.dbc]]. This is for the weapon glowy effects and such. The effect ID is the last column in [[ItemDisplayInfo.dbc]]. They take the ids 0 to 4. Mounts take the id 0 for their rider. Breath (17) is used by CGCamera::FinishLoadingTarget() aswell as some other one. The name above the head of a Unit (CGUnit_C::GetNamePosition) looks for PlayerNameMounted (29), then PlayerName (18).
===Attachment Lookup===
The index of the array defines, which type that attachment is of. Its the same as the list above. The lookups and the id of the animations point in a circle.
struct
{
  uint16_t attachment;
} attachment_lookup[];


*'''nD records of (int16, int16) starting at ofsD'''
==Events==
These events are used for timing sounds for example. You can find the $DTH (death) event on nearly every model. It will play the death sound for the unit.


Maybe a lookup table for animations? Since the numbers happen to be in fixed positions. The first short seems to increase with the position for models with all animations (like characters), the second seems to be flags or a modifier? Or something.
The events you can use depend on the way, the model is used. Dynamic objects can shake the camera, doodads shouldn't. Units can do a lot more than other objects.


Somehow there are some entries, that don't use the $... names but identifiers like "DEST" (destination), "POIN" (point) or "WHEE" (wheel). How they are used? Idk.


Contain indices into the texture animations list, or -1 meaning a static texture.  
struct M2Event
{
  uint32_t identifier;  // mostly a 3 character name prefixed with '$'.
  uint32_t data;        // This data is passed when the event is fired.
  uint32_t bone;        // Somewhere it has to be attached.
  {{Template:Type|C3Vector}} position;  // Relative to that bone of course, animated. Pivot without animating.
  [[#Standard_animation_block|M2TrackBase]] enabled;  // This is a timestamp-only animation block. It is built up the same as a normal [[#Standard_animation_block|AnimationBlocks]], but is missing values, as every timestamp is an implicit "fire now".
} events[];
===Possible Events===
There are a lot more of them. I did not list all up to now.


== Texture animations ==
Bow AnimEvents include $BWA, $BWP, $BWR, $BWS (If present, all of these should be childed to the right hand).


*'''This block contains definitions for texture animations,''' for example, flowing water or lava in some models. The keyframe values are used in the texture transform matrix.
{| class="wikitable"
! width="50" | ID !! width="40" | data !! width="60" | what !! width="60" | Type !! width="200" | seen to be fired on !! width="600" | Description
|-
| $AH[0-3] || — || PlaySoundKit (customAttack[x]) ||  ||  || soundEffect ID is defined by CreatureSoundDataRec::m_customAttack[x]
|-
| $BMD || — || BowMissleDestination || RangedWeapon || Bow Middle ||
|-
| $AIM ||  ||  || Vehicles || CGUnit_C::ComputeMissileTrajectory || Position used as MissileFirePos.
|-
| $ALT ||  || anim_swap_event / DisplayTransition || Unit ||  || CUnitDisplayTransition_C::UpdateState(1) or CUnitDisplayTransition_C::HandleAnimSwapEvent
|-
| $BL[0-3] || — || FootstepAnimEventHit (left) || Unit ||  || Backwards
|-
| $BR[0-3] || — || FootstepAnimEventHit (right) || Unit ||  || Backwards
|-
| $BRT || — || PlaySoundKit (birth) ||  ||  || soundEffect ID is defined by CreatureSoundDatarec::m_birthSoundID
|-
| $BTH || — || Breath || Unit || All situations, where nothing happens or breathing. || Adds Special Unit Effect based on unit state (under water, in-snow, …)
|-
| $BWP || — || PlayRangedItemPull (Bow Pull) || Unit || LoadRifle, LoadBow ||
|-
| $BWR || — || BowRelease || Unit || AttackRifle, AttackBow, AttackThrown ||
|-
| $CAH || — ||  || Unit || Attack*, *Unarmed, ShieldBash, Special* || attack hold? CGUnit_C::HandleCombatAnimEvent
|-
| $CCH || — ||  || Unit || mostly not fired, AttackThrown || Fishing Pole CEffect::DrawFishingString needs this on the model for getting the string attachments.
|-
| $CFM || — ||  || Unit || CGCamera::UpdateMountHeightOrOffset || CGCamera::UpdateMountHeightOrOffset: Only z is used. Non-animated. Not used if $CMA
|-
| $CHD ||  ||  || Unit || not fired || probably does not exist?!
|-
| $CMA || — ||  || Unit ||  || CGCamera::UpdateMountHeightOrOffset: Position for camera
|-
| $CPP || || PlayCombatActionAnimKit || || || parry, anims, depending on some state, also some callback which might do more
|-
| $CSD || soundEntryId || PlayEmoteSound || Unit || Emote* ||
|-
| $CSL || — || release_missiles_on_next_update if has_pending_missiles (left) || Unit || AttackRifle, SpellCast*, ChannelCast* || "x is {L or R} (""Left/right hand"") (?)"
|-
| $CSR || — || release_missiles_on_next_update if has_pending_missiles (right) || Unit || AttackBow, AttackRifle, AttackThrown, SpellCast*, ChannelCast* || "x is {L or R} (""Left/right hand"") (?)"
|-
| $CSS || — || PlayWeaponSwooshSound || || sound played depends on CGUnit_C::GetWeaponSwingType
|-
| $CST || — || release_missiles_on_next_update if has_pending_missiles || Unit || Attack*, *Unarmed, ShieldBash, Special*, SpellCast, Parry*, EmoteEat, EmoteRoar, Kick, ... || $CSL/R/T are also used in CGUnit_C::ComputeDefaultMissileFirePos.
|-
| $CVS ||  ||  ||  ? ||  || Data: SoundEntriesAdvanced.dbc, Sound — Not present in 6.0.1.18179
|-
| $DSE || — || DestroyEmitter || MapObj ||  ||
|-
| $DSL || soundEntryId || DoodadSoundLoop (low priority) | GO ||  ||
|-
| $DSO || soundEntryId || DoodadSoundOneShot || GO ||  ||
|-
| $DTH || — || DeathThud + LootEffect || Unit || Death, Drown, Knockdown || """I'm dead now!"", UnitCombat_C, this plays death sounds and more." Note that this is NOT triggering [[CreatureSoundData.dbc|CreatureSoundDataRec]]::m_soundDeathID, but that is just always triggered as soon as the death animation plays.
|-
| $EAC || — || object package state enter 3, exit 2, 4, 5 ||  ||  ||
|-
| $EDC || — || object package state enter 5, exit 3, 4, 2 ||  ||  ||
|-
| $EMV || — || object package state enter 4, exit 3, 2, 5 ||  ||  ||
|-
| $ESD || — || PlayEmoteStateSound || Unit ||  || soundEffect ID is implicit by currently played emote
|-
| $EWT || — || object package state enter 2, exit 3, 4, 5 ||  ||  ||
|-
| $FD[1-9] || — || PlayFidgetSound ||  ||  || CreatureSoundDataRec::m_soundFidget (only has 5 entries, so don’t use 6-9)
|-
| $FDX || — || PlayUnitSound (stand) ||  ||  || soundEffect ID is defined by CreatureSoundDataRec::m_soundStandID
|-
| $FL[0-3] || — || FootstepAnimEventHit (left) ||  ||  || Foot Left Forward
|-
| $FR[0-3] || — || FootstepAnimEventHit (right) ||  ||  || Foot Right Forward
|-
| $FSD || — || HandleFootfallAnimEvent || Unit || Walk, Run (multiple times), ... || Plays some sound. Footstep? Also seen at several emotes etc. where feet are moved. CGUnit_C::HandleFootfallAnimEvent
|-
| $GC[0-3] || — || GameObject_C_PlayAnimatedSound ||  ||  || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x + 6] ({Custom0, Custom1, Custom2, Custom3})
|-
| $GO[0-5] || — || GameObject_C_PlayAnimatedSound ||  ||  || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x] ({Stand, Open, Loop, Close, Destroy, Opened})
|-
| $HIT || — || PlayWoundAnimKit  || Unit || Attack*, *Unarmed, ShieldBash, Special* || soundEntryId depends on SpellVisualKit
|-
| $KVS ||  ||  ||  ? ||  || MapLoad.cpp -- not found in 6.0.1.18179
|-
| $RL[0-3] || — || FootstepAnimEventHit (left) ||  ||  || Running
|-
| $RR[0-3] || — || FootstepAnimEventHit (right) ||  ||  || Running
|-
| $SCD || — || PlaySoundKit (spellCastDirectedSound) ||  ||  || soundEffect ID is defined by CreatureSoundDataRec::m_spellCastDirectedSoundID
|-
| $SHK || spellEffectCameraShakesID || AddShake || GO ||  ||
|-
| $SHL || — || ExchangeSheathedWeapon (left) ||  || Sheath, HipSheath ||
|-
| $SHR || — || ExchangeSheathedWeapon (right) ||  || Sheath, HipSheath ||
|-
| $SL[0-3] || — || FootstepAnimEventHit (left) ||  || Stop, (JumpEnd), (Shuffle*) || Stop
|-
| $SMD || — || PlaySoundKit (submerged) ||  ||  || soundEffect ID is defined by CreatureSoundDatarec::m_submergedSoundID
|-
| $SMG || — || PlaySoundKit (submerge) ||  ||  || soundEffect ID is defined by CreatureSoundDatarec::m_submergeSoundID
|-
| $SND || soundEntryId || PlaySoundKit (custom) || GO ||  ||
|-
| $SR[0-3] || — || FootstepAnimEventHit (right) ||  || Stop, (JumpEnd), (Shuffle*) || Stop
|-
| $STx ||  ||  || Mounts || MountTransitionObject::UpdateCharacterData || Not seen in 6.0.1.18179 -- x is {E and B} , sequence time is taken of both, pivot of $STB. (Also, attachment info for attachment 0)
|-
| $TRD || — || HandleSpellEventSound || Unit || EmoteWork*, UseStanding* || soundEffect ID is implicit by SpellRec
|-
| $VG[0-8] || — || HandleBoneAnimGrabEvent ||  ||  ||
|-
| $VT[0-8] || — || HandleBoneAnimThrowEvent ||  ||  ||
|-
| $WGG || — || PlayUnitSound (wingGlide) ||  ||  || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingGlideID
|-
| $WL[0-3] || — || FootstepAnimEventHit (left) ||  ||  ||
|-
| $WLB || || || || || Model Weapon Left Bot
|-
| $WLT || || || || || Model Weapon Left Top
|-
| $WNG || — || PlayUnitSound (wingFlap) ||  ||  || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingFlapID
|-
| $WR[0-3] || — || FootstepAnimEventHit (right) ||  ||  ||
|-
| $WRB || || || || || Model Weapon Right Bot
|-
| $WRT || || || || || Model Weapon Right Top
|-
| $WTB || — ||  || Weapons || || Bow Bottom, Weapon Trail Bottom position, also used for Bow String
|-
| $WTT || — ||  || Weapons || || Bow Top, Weapon Trail Top position
|-
| $WWG ||  ||  ||  ? ||  || Calls some function in the Object VMT. -- Not seen in 6.0.1.18179
|-
| DEST ||  ||  ||  ? ||  || exploding ballista, that one has a really fucked up block. Oo
|-
| POIN ||  ||  || Unit || not fired || Data: ?, seen on multiple models. Basilisk for example. (6801)
|-
| WHEE ||  ||  ||  ? ||  || Data: 601+, Used on wheels at vehicles.
|-
| BOTT ||  ||  ||  ? ||  || Purpose unknown. Seen in well_vortex01.m2
|-
| TOP  ||  ||  ||  ? ||  || Purpose unknown. Seen in well_vortex01.m2
|}


nTexAnims records of 0x54 bytes starting at ofsTexAnims, followed by data referenced in these records.
[[Category:Format]]
'''Offset Type Description'''
0x00 AnimationBlock (float, float, float) Translation
0x1C AnimationBlock (float, float, float ???) Rotation?
0x38 AnimationBlock (float, float, float) Scaling?


The three subrecords specify texture transforms. Translation seems to work, producing nice flowing lava and waterfalls.
=References=
:1. Warcraft 3 Art Tools - http://ftp.blizzard.com/pub/war3/other/WarcraftIIIArtTools1.01.zip

Latest revision as of 17:40, 16 March 2024

M2 files (also called MDX) contain model objects. Each M2 file describes the vertices, faces, materials, texture names, animations and properties of one model. M2 files don't have a chunked format like most other WoW formats (except in Legion). Since it is chunked in Legion, all offsets are relative to beginning of the MD21 chunk's data rather than the beginning of the file.

Models are used for doodads (decoration objects), players, monsters and really everything in the game except for Terrain and WMOs.

M2 files do not store all the data for the model in them. Additional model information is stored in these files: .anim, .skin, .phys, .bone, .skel which may vary depending on the client version. Details on how to request and read them are described in the page below.

Header

The header has mostly the layout of number-offset pairs, containing the number of a particular record in the file, and the offset. These appear at fixed places in the header. Record sizes are not specified in the file.

struct 
{
// note: Offsets are for ≥ Wrath!
/*0x000*/  uint32_t magic;                                       // "MD20". Legion uses a chunked file format starting with MD21.
/*0x004*/  uint32_t version;
/*0x008*/  M2Array<char> name;                                   // should be globally unique, used to reload by name in internal clients, empty string in files updated in or after 9.2.0.41462+

/*0x010*/  struct
           {
             uint32_t flag_tilt_x : 1;
             uint32_t flag_tilt_y : 1;
             uint32_t : 1;
#if ≥ BC                                              // TODO: verify version
             uint32_t flag_use_texture_combiner_combos : 1;       // add textureCombinerCombos array to end of data (alt. name: Second_Texture_Material_Override_Combos)
             uint32_t : 1;
#if ≥ Mists
             uint32_t flag_load_phys_data : 1;
             uint32_t : 1;
#if ≥ WoD
             uint32_t flag_unk_0x80 : 1;                         // with this flag unset, demon hunter tattoos stop glowing
                                                                 // since Cata (4.0.1.12911) every model now has this flag
             uint32_t flag_camera_related : 1;                   // TODO: verify version
#if ≥ Legion                                                      // TODO: verify version, these are just added based on where I first saw them -- schlumpf.
             uint32_t flag_new_particle_record : 1;              // In CATA: new version of ParticleEmitters. By default, length of M2ParticleOld is 476. 
                                                                 // But if 0x200 is set or if version is bigger than 271, length of M2ParticleOld is 492.
             uint32_t flag_unk_0x400 : 1;
             uint32_t flag_texture_transforms_use_bone_sequences : 1; // ≥ WoD 0x800 -- When set, texture transforms are animated using the sequence being played on the bone found by index in tex_unit_lookup_table[textureTransformIndex], instead of using the sequence being played on the model's first bone. Example model: 6DU_HellfireRaid_FelSiege03_Creature
             uint32_t flag_unk_0x1000 : 1;
             uint32_t ChunkedAnimFiles_0x2000 : 1;               // seen in various legion models
             uint32_t flag_unk_0x4000 : 1;
             uint32_t flag_unk_0x8000 : 1;                       // seen in UI_MainMenu_Legion
             uint32_t flag_unk_0x10000 : 1;
             uint32_t flag_unk_0x20000 : 1;
             uint32_t flag_unk_0x40000 : 1;
             uint32_t flag_unk_0x80000 : 1;
             uint32_t flag_unk_0x100000 : 1;
             uint32_t flag_unk_0x200000 : 1;                     // apparently: use 24500 upgraded model format: chunked .anim files, change in the exporter reordering sequence+bone blocks before name
#endif
#endif
#endif
#endif
           } global_flags;
        
/*0x014*/  M2Array<M2Loop> global_loops;                        // Timestamps used in global looping animations.
/*0x01C*/  M2Array<M2Sequence> sequences;                       // Information about the animations in the model.
/*0x024*/  M2Array<uint16_t> sequenceIdxHashById;               // Mapping of sequence IDs to the entries in the Animation sequences block.
#if ≤ BC
           M2Array<M2SequenceFallback> playable_animation_lookup;
#endif
/*0x02C*/  M2Array<M2CompBone> bones;                           // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones (Wrath)
                                                                                      => World\Expansion01\Doodads\Auchindoun\Passivedoodads\Bridge_FX\Auchindoun_Bridge_Spirits_Flying.m2 has 305 bones (BC)
/*0x034*/  M2Array<uint16_t> boneIndicesById;                   //Lookup table for key skeletal bones. (alt. name: key_bone_lookup)
/*0x03C*/  M2Array<M2Vertex> vertices;
#if ≤ BC
           M2Array<M2SkinProfile> skin_profiles;
#else
/*0x044*/  uint32_t num_skin_profiles;                           // Views (LOD) are now in .skins.
#endif
         
/*0x048*/  M2Array<M2Color> colors;                             // Color and alpha animations definitions.
/*0x050*/  M2Array<M2Texture> textures;
/*0x058*/  M2Array<M2TextureWeight> texture_weights;            // Transparency of textures.
#if ≤ BC
           M2Array<M2TextureFlipbook> texture_flipbooks;                         // never seen in file, 4 uint32_t fields. (M2Track<ushort>)
#endif
/*0x060*/  M2Array<M2TextureTransform> texture_transforms;
/*0x068*/  M2Array<uint16_t> textureIndicesById;                // (alt. name: replacable_texture_lookup)
/*0x070*/  M2Array<M2Material> materials;                       // Blending modes / render flags.
/*0x078*/  M2Array<uint16_t> boneCombos;                        // (alt. name: bone_lookup_table)
/*0x080*/  M2Array<uint16_t> textureCombos;                     // (alt. name: texture_lookup_table)
/*0x088*/  M2Array<uint16_t> textureCoordCombos;           // (alt. name: tex_unit_lookup_table, texture_mapping_lookup_table)
/*0x090*/  M2Array<uint16_t> textureWeightCombos;               // (alt. name: transparency_lookup_table)
/*0x098*/  M2Array<uint16_t> textureTransformCombos;            // (alt. name: texture_transforms_lookup_table)
         
/*0x0A0*/  CAaBox bounding_box;                                 // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height
/*0x0B8*/  float bounding_sphere_radius;                         // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …)
/*0x0BC*/  CAaBox collision_box;
/*0x0D4*/  float collision_sphere_radius;
         
/*0x0D8*/  M2Array<uint16_t> collisionIndices;                    // (alt. name: collision_triangles)
/*0x0E0*/  M2Array<C3Vector> collisionPositions;                  // (alt. name: collision_vertices)
/*0x0E8*/  M2Array<C3Vector> collisionFaceNormals;                // (alt. name: collision_normals) 
/*0x0F0*/  M2Array<M2Attachment> attachments;                     // position of equipped weapons or effects
/*0x0F8*/  M2Array<uint16_t> attachmentIndicesById;               // (alt. name: attachment_lookup_table)
/*0x100*/  M2Array<M2Event> events;                               // Used for playing sounds when dying and a lot else.
/*0x108*/  M2Array<M2Light> lights;                               // Lights are mainly used in loginscreens but in wands and some doodads too.
/*0x110*/  M2Array<M2Camera> cameras;                             // The cameras are present in most models for having a model in the character tab. 
/*0x118*/  M2Array<uint16_t> cameraIndicesById;                   // (alt. name: camera_lookup_table)
/*0x120*/  M2Array<M2Ribbon> ribbon_emitters;                     // Things swirling around. See the CoT-entrance for light-trails.
/*0x128*/  M2Array<M2Particle> particle_emitters;
         
#if ≥ BC                                                       // TODO: verify version
           if (flag_use_texture_combiner_combos)
           {
/*0x130*/    M2Array<uint16_t> textureCombinerCombos;           // (alt. name: Second_Texture_Material_Override_Combos) When set, m2 multitexturing will use the second material from here, instead of current index material + 1, for blending with the first texture
           }
#endif
} header;

Types

struct M2Bounds {
  CAaBox extent;
  float radius;
};
template<typename T>
struct M2Array {
  uint32_t size;
  uint32_t offset; // pointer to T, relative to begin of m2 data block (i.e. MD21 chunk content or begin of file)
};
struct M2TrackBase {
  uint16_t trackType;
  uint16_t loopIndex;
  M2Array<M2SequenceTimes> sequenceTimes;
};
template<typename T> 
struct M2PartTrack {
  M2Array<fixed16> times;
  M2Array<T> values;
};
template<typename T> 
struct M2SplineKey {
  T value;
  T inTan;
  T outTan;
};
struct M2Range {
  uint32_t minimum;
  uint32_t maximum;
};

Versions

Files get handled differently depending on this! Ranges are inclusive. Note that these versions are only rough estimates of their range. Prefer looking at the model you're opening rather than relying on the expansion mapping here.

Version Version (Major, Minor) Expansion
272-274 1.16-1.18 Legion, Battle for Azeroth, Shadowlands
272 1.16 Mists of Pandaria, Warlords of Draenor
265-272 1.9-1.16 Cataclysm
264 1.8 Wrath of the Lich King
260-263 1.4-1.7 The Burning Crusade
256-257 1.0-1.1 Classic
256 1.0 Pre-Release

The version is most likely a double-byte with major and minor version. This makes version 256 to 1.0, and 274 to 1.18 respectively.

Chunks

This section only applies to versions ≥ Legion (7.0.1.20740).

From Legion and up, the file might be chunked instead. If this is the case, the magic will be anything but 'MD20' and the m2 data will be in the 'MD21' chunk. If the first magic is 'MD20', it will be loaded just fine like it did previously. Note that the chunks can be in any order with MD21 often being first.

NOTE: Unlike all other chunked formats in WoW, chunk names in M2 are NOT reversed. Example: AFID == AFID in file.

MD21

This section only applies to versions ≥ Legion (7.0.1.20740).

The MD21 chunk contains the not yet chunked data, just like in the old format. The content literally is an pre legion file, starting with the MD20 magic. This also implies that all offsets inside this chunk are relative to the chunk, not the file.

M2Data pre_legion_style_data;

PFID

This section only applies to versions ≥ Legion (7.0.1.20740). This used to be filename based, using ${basename}.phys.
uint32_t phys_file_id;

SFID

This section only applies to versions ≥ Legion (7.0.1.20740). This used to be filename based, using ${basename}${view}.skin and ${basename}_lod${lodband}.skin.
uint32_t skinFileDataIDs[header.nViews];
uint32_t lod_skinFileDataIDs[lodBands /* 2? */];

Some model files, for example 'Creature\NightborneFemaleCitizen\NightborneFemaleCitizen.m2' have 4 skin files and 2 lod files but only 20 bytes are in chunk. In chunk there are 4 skins and 1 lod present.

Lod skins are selected based on distance to entity/doodad and chosen based on GetCVar("entityLodDist")/X and GetCVar("doodadLodDist")/X where X - distance. Lods are ignored when "M2UseLOD" CVar is set to 0.

AFID

This section only applies to versions ≥ Legion (7.0.1.20740). This used to be filename based, using ${basename}${anim_id}-${sub_anim_id}.anim.
struct
{
  uint16_t anim_id;
  uint16_t sub_anim_id;
  uint32_t file_id;        // might be 0 for "none" (so this is probably not sparse, even if it could be)
} anim_file_ids[];

BFID

This section only applies to versions ≥ Legion (7.0.1.20740). This used to be filename based, using ${basename}_${i}.bone.
uint32_t boneFileDataIDs[];

TXAC

This section only applies to versions ≥ Legion. It is unknown what this replaced. Exact build unknown, not the first one though.
struct {
  char unk[2]; // likely used in CM2SceneRender::SetupTextureTransforms and uploaded to the shader directly. 0 otherwise.
} texture_ac[m2data.header.materials.count + m2data.header.particles.count];


This chunk doesn't seem to be used directly. Inside CParticleEmitter2 class there are non-null checks that deal with selection of VertexBufferFormat for particles. Apart from that, the usage of these fields is unknown

EXPT

This section only applies to versions ≥ Legion. Appears to partially replace M2ParticleOld's variables. Exact build unknown, not the first one though.
struct {
 float zSource;
 float colorMult;
 float alphaMult;
} extended_particle[m2data.header.particles.count];

Probably outdated chunk after introduction of EXP2 chunk. If EXP2 doesnt exist, client tries to reconstruct it with data from EXPT chunk.

EXP2

This section only applies to versions ≥ Legion (7.3.???). Appears to partially replace M2ParticleOld's variables. Exact build unknown.
struct M2ExtendedParticle
{
  float zSource;
  float colorMult;
  float alphaMult;
  M2PartTrack<fixed16> alphaCutoff;
};

struct M2InitExtendedParticleArray
{
  M2Array<M2ExtendedParticle> content;
} exp2;


The length of this M2Array is the same as length of particle_emitters

alphaCutoff is for alphaTest per particle. Index into alphaCutoff is particle's current lifetime

colorMult is applied against particle's diffuse color

alphaMult is applied against particle's opacity.

PABC

This section only applies to versions ≥ Legion (7.3.???). Appears to replace #Animation_Lookup from old file. Exact build unknown.
M2Array<uint16t> m_replacementParentSequenceLookups; // only seen in quillboarbrute*.m2. Contains AnimationIds

Replaces parentSequencesLookups. But unlike header.sequence_lookups of parent model, this is straight array and not a map. If index with target animation is not found in here, parentSequencesLookups are used instead.

This chunk called BlacklistAnimData in client.

Client doesnt seem to use found index and thus whole array is used only to check if the target animation is present.

M2InitBlacklistAnimData: sequenceIds

PADC

This section only applies to versions ≥ Legion (7.3.???). Moves texture weights from old file to a chunk. Exact build unknown.

Defines replacement for header.texture_weights (WHY?)

struct PADC {
    M2Array<M2TextureWeight> texture_weights;
}

M2InitParentAnimData: parentTextureWeights

PSBC

This section only applies to versions ≥ Legion (7.3.???). Exact build unknown.

Defines ParentSequenceBounds

M2Array<M2Bounds> parentSequenceBounds;

M2InitParentSequenceBoundsData: M2Bounds

PEDC

This section only applies to versions ≥ Legion (7.3.???). Exact build unknown.
M2Array<M2TrackBase> parentEventData;

M2InitParentEventData: eventTracks

SKID

This section only applies to versions ≥ Legion (7.3.???). This used to be filename based, using ${basename}.skel. Exact build unknown.
uint32_t SKeletonfileID;    // links to M2/.skel

TXID

This section only applies to versions ≥ Battle (8.0.1.26629). Replaces the filename for {{#Textures}} with hardcoded type..

Replaces in-file texture filenames.

struct {
  uint32_t fileDataID;
} textureID[]

LDV1

This section only applies to versions ≥ Battle (8.0.1.26629).

Defines LodData

struct LodData
{
  uint16 unk0; 
  uint16 lodCount; //maxLod = lodCount-1;  
  float unk2_f;
  uint8_t particleBoneLod[4]; //lod serves as indes into this array
  _DWORD unk4;
};

Somehow defines _lod%0d.skin files. On pandarenfemale.m2, lodCount == 4. SFID has 7 files first 4 are ordinary .skin files and last 3 are _lod%0d.skin files. Enumeration for _lod%0d.skin files for that model starts from 1, and last file in SFID is pandarenfemale_lod03.skin So technically maxLod indeed represents maximum Lod

unk2_f is used in formula, but it's purpose is unknown

fmaxf(fminf(740.0 / unk2_f, 5.0), 0.5);

LodData.particleBoneLod works this way: Each model has current lod which is [0..3]. Next:

  if ( lod < 1 )
    result = 0;
  
  if ( LodData)
    result = (0x10000 << LodData->particleBoneLod[lod]);
  else
    result = (0x10000 << (lod- 1));

...
//For each ParticleEmitter and related M2Particle record
if ( result & M2CompBone[M2Particle->old.boneIndex].flags ) {
  //Do not animate this emitter
}


Deamon (talk)

RPID

This section only applies to versions ≥ Battle (8.1.0.27826). Replaces #M2ParticleOld's recursion_model_filename.
struct {
  uint32_t fileDataID; // referance a m2
} recursive_particle_models[particle count];

GPID

This section only applies to versions ≥ Battle (8.1.0.27826). Replaces #M2ParticleOld's geometry_model_filename.
struct {
  uint32_t fileDataID; // referance a m2
} geometry_particle_models[particle count];

WFV1

This section only applies to versions ≥ Battle (8.2.0.30080).
struct WFV1 {
  // unknown
};

WFV1 = WaterFallVersion1 Tells that model has PBR-ish stuff and normal map. It uses separate render path from usual M2.

First tested on waterfall in 8.2.0 (fdid 2445860). And at that moment it was WFV1.

Later this same tech was used to create different models, which have no relation to waterfalls. But internally even the name "waterfall" was kept.

Even stuff around areas in Shadowlands map is still called waterfall internally (fdid 3445776 for example). These models have WFV3 chunk.

WFV2

This section only applies to versions ≥ Battle (8.2.0.30080).
struct WFV2 {
  // unknown
};

PGD1

This section only applies to versions ≥ Vanilla (1.13.2.30172).

ParticleGeosetData

struct PGD1Entry {
   uint16_t geoset;
};
M2Array<PGD1Entry> p_g_d_v1; // count is equivalent to particle count

M2InitParticleGeosetData: m_emitterSelectionGroup

This sets geoset for each Particle emitter. And with this value Particle Emitter start to obey same geoset rules as in M2SkinSection.

WFV3

This section only applies to versions ≥ SL (9.0.1.33978).
 struct WaterFallDataV3 {
   float bumpScale;  //Passed to vertex shader
   float value0_x;
   float value0_y;
   float value0_z;
   float value1_w;
   float value0_w;
   float value1_x;
   float value1_y;
   float value2_w;
   float value3_y;
   float value3_x;
   CImVector basecolor; // in rgba (not bgra)
   uint16_t flags;
   uint16_t unk0;
   float values3_w;
   float values3_z;
   float values4_y;
   float unk1;
   float unk2;
   float unk3;
   float unk4;
 }

"value" fields are passed directly to fragment shader.

PFDC

This section only applies to versions ≥ SL (9.0.1.33978).
PHYS physics;
char PADDING[6];  // follows right after the last chunk in PHYS, or it could be data, only seen 0's
                  // likely it isn't 6 but "whatever is needed until the next 8 (or 16?) byte alignment". --Schlumpf (talk)

Contains inline physics information in the same structure as the .phys files.

EDGF

This section only applies to versions ≥ SL (9.0.1.33978).
struct EDGF {
/*0x00*/ float _0x0[2];
/*0x08*/ float _0x8;
/*0x0C*/ char _0xC[0xC]
} edgf[];

EdgeFade. This data is applied to mesh only if M2Batch.flags2 has 0x8 flag

NERF

This section only applies to versions ≥ SL (9.0.1.33978).
struct NERF {
   C2Vector coefs;
};

Something related to calculation of alpha for whole model and related to distance. Components of coefs form ration with squared length of instanced model radius.

(coefs.x - squaredRadius) / (coefs.x - coefs.y)

And this value is used as multiplier for model instace's alpha.

Deamon (talk) 08:35, 30 April 2020 (CEST)

DETL

This section only applies to versions ≥ SL (9.0.1.34365).
struct 
{
  uint16_t flags;
  uint16_t packedFloat0;
  uint16_t packedFloat1; // multiplier for M2Light.diffuse_color
  uint16_t unk0;
  uint32_t unk1;
} DETL_recs[m2data.header.lights.count];

Something related to lights

DBOC

This section only applies to versions ≥ SL (9.0.1.33978).
struct 
{
  float unk1_1;
  float unk1_2;
  uint32 unk1_3;
  uint32 unk1_4;
  float unk2_1;
  float unk2_2;
  uint32 unk2_3;
  uint32 unk2_4;
} DBOC;

Observed as 32 bytes in 9ARD_NebulaCloud_B01.m2, might be 2 entries, might not be.

AFRA

This section only applies to versions ≥ DF.

Not observed in any files yet, presumably added in DF.

Skeleton and animation

Global sequences

A list of timestamps that act as upper limits for global sequence ranges.

struct M2Loop
{
  uint32_t timestamp;
} loops[];

Standard animation block

  • Wrath uses a single-timeline approach, chaining all animations into one long piece and separating them via begin and end given in animation data. ≥ Wrath, each animation has an own timeline.
  • Animation blocks contain a list of lists of timestamps and a list of lists of values, where the first list is by animation and the second one by timestamp-entry.
  • Many values that change with time are specified using blocks like the following.
 template<typename T>
 struct M2Array
 {
 /*0x00*/  uint32_t number;
 /*0x04*/  uint32_t offset_elements;
 /*0x08*/
 };
 struct M2TrackBase
 {
 /*0x00*/  uint16_t interpolation_type;
 /*0x02*/  uint16_t global_sequence;
#if < Wrath
   M2Array<pair<uint32_t>> interpolation_ranges;   // no longer required ≥ Wrath, as implicit by minimum and maximum timestamp per sequence.
   M2Array<uint32_t> timestamps;
#else
 /*0x04*/  M2Array<M2Array<uint32_t>> timestamps;
#endif
 /*0x0C*/
 };
 
 template<typename T>
 struct M2Track : M2TrackBase
 {
 /*0x00*/  // base 
#if < Wrath 
   M2Array<T> values;
#else
 /*0x0C*/  M2Array<M2Array<T>> values;
#endif
 /*0x14*/
 };
  • Thus, as example, with
 M2CompBone b;

one may get the number of animations having translation information with

 b.translation.timestamps.number

and the number of timestamps in the first animation using

 b.translation.timestamps.elements[0].number

and the first timestamp value of the first animation via

 b.translation.timestamps.elements[0].elements[0]

The actual translation vector for animation 0 at timestamp 0 is at

 b.translation.values.elements[0].elements[0]
  • Some timestamps/values.elements entries may have number/elements = 0, if for that animation id no animation is given.
  • .anim files are just a blob of data which may as well be in the main model file, that is pointed to by the first array_ref layer.
  • [model file name][animation id]-[animation sub-id].anim
  • it seems like it is possible to detect if animation data is stored in-m2 or externally via
    • All animations which have flags & 0x20 are stored internally.
    • Animations which do not have flags & 0x20 are not stored internally.
    • Animations which do not have flags & 0x20 AND do not have flags & 0x40 are in .anim files
    • Animations which do not have flags & 0x20 AND DO have flags 0x40 are stored... somewhere. I have no clue.

Global Sequences

If a block has a sequence >= 0, the block has a completely separate max timestamp. This is the value in the model's ofsGlobalSequences table; index into that table with this sequence value and use that as the block's max timestamp. Blocks that use these global sequences also only have one track, so at the same time as clipping the current timestamp to the max time above, interpolated value should always be taken from track 0 in the block.

A global sequence is completely unrelated to animations. It just always loops. This way, the sequence is not interrupted when an animation is launched.

This appears to be frequently used by models that don't have more conventional animations (login screen animations, items/weapons with animated effects, etc).

-- Rour, additionally, these sequences can be longer or shorter than whatever animation is running for a given model, so I recommend taking a global scene timestamp and then clipping that value into the given max timestamp range. Otherwise animations will appear to reset when the regular animation loops, which is not good.

Interpolation

  • If the interpolation type is 0, then values will change instantly at the timestamp, with no interpolation whatsoever.
  • If the interpolation type is 1, then the block linearly interpolates between keyframe values (lerp for vectors/colours, nlerp for quaternions).
  • If the interpolation type is 2, then cubic bezier spline interpolation is used. This is only valid for M2SplineKey tracks. When interpolating between two spline keys, the first control point is the first spline key's value, the second control point is the first spline key's tanOut, the third control point is the second spline key's tanIn, and the fourth control point is the second spline key's value.
  • If the interpolation type is 3, then cubic hermite spline interpolation is used. This is only valid for M2SplineKey tracks. When interpolating between two spline keys, the starting point is the first spline key's value, the starting tangent is the first spline key's tanOut, the ending tangent is the second spline key's tanIn, and the ending point is the second spline key's value.

NOTE: There is confusion about type 2 and 3 being hermite/bezier or bezier/hermite. Alpha says that 2 = hermite, WoD says that 2 = bezier. This was changed when the format went from MDL to M2. --Schlumpf (talk) 01:53, 4 September 2017 (CEST), --Barncastle (talk) 19:15, 14 November 2018 (GMT)

In WotLK 2 appears to be bezier, which pretty much confirms it being bezier for wotlk+. TBC and classic need further checking though. -- Skarn (talk) 01:38, 16 June 2018 (CEST)

.anim files

This section only applies to versions ≥ Wrath.

Low priority sequences (e.g. emotes, one shot animations) are in extra files to allow for lazy loading. These files are raw data of timestamps and values for animation blocks. Blizzard's exporter prefers to align blocks to a 16 byte boundary, but that's not required.

The client loads .anim files if (M2Sequence.flags & 0x130 ) == 0. The .anim file to use is "%s%04d-%02d.anim" % (model_filename_without_extension, anim.id, anim.sub_anim_id).

Legion 24500

In Legion, these files are optionally chunked now. They are chunked either

  • if M2 header's 0x200000 flag is set and thus the new mid expansion format change is used
  • if the M2 has a .skel file

For new format M2s, .anim is pretty much unchanged except that there is the AFM2 chunk header. The AFSA and AFSB chunks do not appear in that case. If it is a .skel file based model, the chunks are present and animation data is split into bone and attach data. The AFM2 chunk then contains the animation data for ????, the AFSA chunk that for attachments and the AFSB chunk that for bones. See .skel files for that.

AFM2

The same content as an old anim file would have. In fact, files that were just converted to the new format are bit identical except for the chunk header.

AFSA

skeleton data for attachments

AFSB

skeleton data for bones

Animation sequences

List of animations present in the model.

struct M2Sequence
{
/*0x00*/  uint16_t id;                   // Animation id in AnimationData.dbc
/*0x02*/  uint16_t variationIndex;       // Sub-animation id: Which number in a row of animations this one is.
#if ≤ BC
/*0x04*/  uint32_t start_timestamp;
/*0x08*/  uint32_t end_timestamp;
#else
/*0x04*/  uint32_t duration;             // The length of this animation sequence in milliseconds.
#endif
/*0x08*/  float movespeed;               // This is the speed the character moves with in this animation.
/*0x0c*/  uint32_t flags;                // See below.
/*0x10*/  int16_t frequency;             // This is used to determine how often the animation is played. For all animations of the same type, this adds up to 0x7FFF (32767).
/*0x12*/  uint16_t _padding;
/*0x14*/  M2Range replay;                // May both be 0 to not repeat. Client will pick a random number of repetitions within bounds if given.
#if version < ??? < 6.0.1
/*0x1c*/  uint32_t blendTime;
#else
/*0x1c*/  uint16_t blendTimeIn;          // The client blends (lerp) animation states between animations where the end and start values differ. This specifies how long that blending takes. Values: 0, 50, 100, 150, 200, 250, 300, 350, 500.
/*0x1e*/  uint16_t blendTimeOut;         // The client blends between this sequence and the next sequence for blendTimeOut milliseconds.
#endif
                                 // For both blendTimeIn and blendTimeOut, the client plays both sequences simultaneously while interpolating between their animation transforms.
  M2Bounds bounds;
/*0x20*/  int16_t variationNext;         // id of the following animation of this AnimationID, points to an Index or is -1 if none.
/*0x22*/  uint16_t aliasNext;            // id in the list of animations. Used to find actual animation if this sequence is an alias (flags & 0x40)
/*0x24*/
} sequences[];

--Koward (talk) 09:50, 18 December 2015 (UTC) In M2 v274 (Legion), it looks like blend_time has been divided in two uint16_t, and for standard animations the old blend_time is duplicated in both fields (ex : uint32 150 becomes two uint16 150). Maybe start and end blend_time values ? See Creature/GennGreymane/GennGreymane.m2 .

Flags

One thing I saw in the source is that "-1 animationblocks" in bones wont get parsed if 0x20 is not set.

Flag Description
0x01 Sets 0x80 when loaded. (M2Init)
0x02
0x04
0x08
0x10 apparently set during runtime in CM2Shared::LoadLowPrioritySequence for all entries of a loaded sequence (including aliases)
0x20 primary bone sequence -- If set, the animation data is in the .m2 file. If not set, the animation data is in an .anim file. Was named 'looped animation' by schlumpf years ago, without source.
0x40 has next / is alias (To find the animation data, the client skips these by following aliasNext until an animation without 0x40 is found.)
0x80 Blended animation (if either side of a transition has 0x80, lerp between end->start states, unless end==start by comparing bone values)
0x100 sequence stored in model ?
0x200 Signals that uint32_t blendTime is two fields: blendTimeIn and blendTimeOut. (Actual usage and influence of blendTimeIn and blendTimeOut depends on blendTimeOperation, which is not stored in files, but code and context dependent)
0x400
0x800 seen in Legion 24500 models
0x1000
0x2000

-- Rour, some animations rely on blending to look right. The MoP mage CM shoulders only animate half of their movement and rely on lerping back to the start position to look correct.

Animation Lookup

Hash table for Animations in AnimationData.dbc. For a list of animation names, see M2/AnimationList.

struct
{
  int16_t animation_index; // Index at ofsAnimations which represents the animation in AnimationData.dbc. -1 if none.
} animation_lookups[];

The hash used is anim_id % num_buckets. If a bucket is used, a stride of n^2 is added with n = 1, 2, … until the entry is matching:

M2Sequence* find_entry (uint32_t anim_id)
{
  size_t i (anim_id % animation_lookups.count);
  
  for (size_t stride (1); true; ++stride)
  {
    if (animation_lookups[i] == -1)
    {
      return nullptr;
    }
    if (animation_sequences[animation_lookups[i]].id == anim_id)
    {
      return &animation_sequences[i];
    }

    i = (i + stride * stride) % animation_lookups.count;
    // so original_i + 1, original_i + 1 + 4, original_i + 1 + 4 + 9, …
  }

  [[unreachable]];
}

The entry referenced is the first in the `nextAlias` chain of a given animation id. Thus, num_buckets < num_animations, even if a model would have all animations multiple times.

Playable Animation Lookup

This section only applies to versions ≤ BC. Partially inlined into M2Sequences.

Lookup table for Playable Animation in AnimationData.dbc

Offset   Type     Description
0x00   int16     Fallback Animation ID in AnimationData.dbc
0x02   int16     Flags (0, 1, 3 seen)

Models don't present all animations sequences. This table maps from global animation list to available animation for the current model. The engine uses it to know which animation clip to play. That's why there are a lot of zeros ("Stand") for creatures.

Flags are used to modify how the clip should be played:

Value       Meaning
0       Play normal
1       Play backwards?
3       Freeze

For instance, the "HumanMale/HumanMale.m2" model has just one "Loot" animation sequence. "LootHold" and "LootUp" are obtained with flags 3 and 1.

Bones

struct M2CompBone                 // probably M2Bone ≤ Vanilla
{
  int32_t key_bone_id;            // Back-reference to the key bone lookup table. -1 if this is no key bone.
  enum
  {
    ignoreParentTranslate = 0x1,
    ignoreParentScale = 0x2,
    ignoreParentRotation = 0x4,
    spherical_billboard = 0x8,
    cylindrical_billboard_lock_x = 0x10,
    cylindrical_billboard_lock_y = 0x20,
    cylindrical_billboard_lock_z = 0x40,
    transformed = 0x200,
    kinematic_bone = 0x400,       // MoP+: allow physics to influence this bone
    helmet_anim_scaled = 0x1000,  // set blend_modificator to helmetAnimScalingRec.m_amount for this bone
    something_sequence_id = 0x2000, // <=bfa+, parent_bone+submesh_id are a sequence id instead?!
  };
  uint32_t flags;                 
  int16_t parent_bone;            // Parent bone ID or -1 if there is none.
  uint16_t submesh_id;            // Mesh part ID OR uDistToParent?
  union {                         // only ≥ BC?
    struct {
      uint16_t uDistToFurthDesc;
      uint16_t uZRatioOfChain;
    } CompressData;               // No model has ever had this part of the union used.
    uint32_t boneNameCRC;         // these are for debugging only. their bone names match those in key bone lookup.
  };
  M2Track<C3Vector> translation;
#if ≤ Vanilla
  M2Track<C4Quaternion> rotation;
#else
  M2Track<M2CompQuat> rotation;   // compressed values, default is (32767,32767,32767,65535) == (0,0,0,1) == identity
#endif
  M2Track<C3Vector> scale;
  C3Vector pivot;                 // The pivot point of that bone.
} bones[];

The bone indices in the vertex definitions seem to index into this data.

Billboards

The billboarding bits are used for various things:

  • Light halos around lamps must always face the viewer
  • The cannonball stack model (in the Deadmines or Booty Bay), where each cannonball is a crude hemisphere, they always face the viewer to create the illusion of actual cannonballs.

Spherical and cylindrical billboard bits are mutually exclusive. Only one of them can be used for the bone.

Bone Lookup Table

Lookup table for bones referenced from M2SkinSection.

struct 
{
  uint16_t bone;
} bone_lookup_table[];

Key-Bone Lookup

Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 27 for the most models up to WotLK, then 35, whatever the biggest named key bone was at the given expansion. For static models it is mostly 1.

struct 
{
  uint16_t bone; // -1 if none
} key_bone_lookup[];

Key Bone Names

The key bone name table is collapsed by default to reduce page length. Click expand on the right to expand.

Confirmed names (matching CRC) highlighted in green, unverified/made up names are unmarked.

ID Name Notes
00 ArmL
01 ArmR
02 ShoulderL
03 ShoulderR
04 SpineLow "Upper Body" in animkits, if not present use Head
05 Waist
06 Head
07 Jaw
08 IndexFingerR
09 MiddleFingerR
10 PinkyFingerR
11 RingFingerR
12 ThumbR
13 IndexFingerL
14 MiddleFingerL
15 PinkyFingerL
16 RingFingerL
17 ThumbL
18 $BTH
19 $CSR
20 $CSL
21 _Breath
22 _Name
23 _NameMount
24 $CHD
25 $CCH
26 Root
27 Wheel1 ≥ Wrath
28 Wheel2 ≥ Wrath
29 Wheel3 ≥ Wrath
30 Wheel4 ≥ Wrath
31 Wheel5 ≥ Wrath
32 Wheel6 ≥ Wrath
33 Wheel7 ≥ Wrath
34 Wheel8 ≥ Wrath, higher bone IDs not present in WotLK
35 FaceAttenuation
36 EXP_C1_Cape1 Previous unconfirmed name was "CapeParent"
37 EXP_C1_Cape2 Previous unconfirmed name was "CapeChild1"
38 EXP_C1_Cape3 Previous unconfirmed name was "CapeChild2"
39 EXP_C1_Cape4 Previous unconfirmed name was "CapeChild3"
40 EXP_C1_Cape5 Previous unconfirmed name was "CapeChild4"
41 EXP_C1_Tail1
42 EXP_C1_Tail2
43 EXP_C1_LoinBk1 Previous unconfirmed name was "TabardParent"
44 EXP_C1_LoinBk2 Previous unconfirmed name was "TabardChild1"
45 EXP_C1_LoinBk3 Previous unconfirmed name was "TabardChild2"
48 EXP_C1_Spine2 Previous unconfirmed name was "UpperBodyParent"
49 EXP_C1_Neck1 Previous unconfirmed name was "NeckParent"
50 EXP_C1_Neck2 Previous unconfirmed name was "NeckChild1"
51 EXP_C1_Pelvis1 Previous unconfirmed name was "LowerBodyParent"
52 Buckle
53 Chest
54 Main
55 EXP_R1_Leg1Twist1 Previous unconfirmed name was "LegR"
56 EXP_L1_Leg1Twist1 Previous unconfirmed name was "LegL"
57 EXP_R1_Leg2Twist1 Previous unconfirmed name was "KneeR"
58 EXP_L1_Leg2Twist1 Previous unconfirmed name was "KneeL"
59 FootL
60 FootR
61 ElbowR
62 ElbowL
63 EXP_L1_Shield1 Previous unconfirmed name was "Unk_ElbowL_Child"
64 HandR
65 HandL
66 WeaponR
67 WeaponL
68 SpellHandL Previous unconfirmed name was "Unk_WristL_Child"
69 SpellHandR Previous unconfirmed name was "Unk_WristR_Child"
70 EXP_R1_Leg1Twist3 Previous unconfirmed name was "KneeR_UpperRig"
71 EXP_L1_Leg1Twist3 Previous unconfirmed name was "KneeL_UpperRig"
72 EXP_R1_Arm1Twist2 Previous unconfirmed name was "ArmR_2"
73 EXP_L1_Arm1Twist2 Previous unconfirmed name was "ArmL_2"
74 EXP_R1_Arm1Twist3 Previous unconfirmed name was "ElbowR_UpperRig"
75 EXP_L1_Arm1Twist3 Previous unconfirmed name was "ElbowL_UpperRig"
76 EXP_R1_Arm2Twist2 Previous unconfirmed name was "ForearmR"
77 EXP_L1_Arm2Twist2 Previous unconfirmed name was "ForearmL"
78 EXP_R1_Arm2Twist3 Previous unconfirmed name was "WristR_UpperRig"
79 EXP_L1_Arm2Twist3 Previous unconfirmed name was "WristL_UpperRig"
80 ForearmR
81 ForearmL
82 EXP_R1_Arm1Twist1
83 EXP_L1_Arm1Twist1
84 EXP_R1_Arm2Twist1
85 EXP_L1_Arm2Twist1
86 EXP_R1_FingerClawA1
87 EXP_R1_FingerClawB1
88 EXP_L1_FingerClawA1
89 EXP_L1_FingerClawB1
190 _BackCloak
191 face_hair_00_M_JNT
192 face_beard_00_M_JNT
193 face_cheek_02_L_SkinPoint IGC (in-game cinematic) models only
194 face_cheek_02_R_SkinPoint IGC models only
195 face_eyeCornerIn_00_L_SkinPoint IGC models only
196 face_eyeCornerIn_00_R_SkinPoint IGC models only
197 face_eyeCornerOut_00_L_SkinPoint IGC models only
198 face_eyeCornerOut_00_R_SkinPoint IGC models only
199 face_eyebrow_00_L_SkinPoint IGC models only
200 face_eyebrow_00_M_SkinPoint IGC models only
201 face_eyebrow_00_R_SkinPoint IGC models only
202 face_eyebrow_01_L_SkinPoint IGC models only
203 face_eyebrow_01_R_SkinPoint IGC models only
204 face_eyebrow_02_L_SkinPoint IGC models only
205 face_eyebrow_02_R_SkinPoint IGC models only
206 face_eyebrow_03_L_SkinPoint IGC models only
207 face_eyebrow_03_R_SkinPoint IGC models only
208 face_eyelidBot_00_L_SkinPoint IGC models only
209 face_eyelidBot_00_R_SkinPoint IGC models only
210 face_eyelidBot_01_L_SkinPoint IGC models only
211 face_eyelidBot_01_R_SkinPoint IGC models only
212 face_eyelidBot_02_L_SkinPoint IGC models only
213 face_eyelidBot_02_R_SkinPoint IGC models only
214 face_eyelidTop_00_L_SkinPoint IGC models only
215 face_eyelidTop_00_R_SkinPoint IGC models only
216 face_eyelidTop_01_L_SkinPoint IGC models only
217 face_eyelidTop_01_R_SkinPoint IGC models only
218 face_eyelidTop_02_L_SkinPoint IGC models only
219 face_eyelidTop_02_R_SkinPoint IGC models only
220 face_noseBridge_00_L_SkinPoint IGC models only
221 face_noseBridge_00_R_SkinPoint IGC models only
222 face_overEye_00_L_SkinPoint IGC models only
223 face_overEye_00_R_SkinPoint IGC models only
224 face_overOuterEye_00_L_SkinPoint IGC models only
225 face_overOuterEye_00_R_SkinPoint IGC models only
226 face_underEye_00_L_SkinPoint IGC models only
227 face_underEye_00_R_SkinPoint IGC models only
228 face_cheekPuff_00_L_SkinPoint IGC models only
229 face_cheekPuff_00_R_SkinPoint IGC models only
230 face_cheek_00_L_SkinPoint IGC models only
231 face_cheek_00_R_SkinPoint IGC models only
232 face_cheek_01_L_SkinPoint IGC models only
233 face_cheek_01_R_SkinPoint IGC models only
234 face_chin_00_L_SkinPoint IGC models only
235 face_chin_00_M_SkinPoint IGC models only
236 face_chin_00_R_SkinPoint IGC models only
237 face_ear_00_L_SkinPoint IGC models only
238 face_ear_00_R_SkinPoint IGC models only
239 face_jaw_01_M_SkinPoint IGC models only
240 face_jowl_00_L_SkinPoint IGC models only
241 face_jowl_00_R_SkinPoint IGC models only
242 face_jowl_01_L_SkinPoint IGC models only
243 face_jowl_01_R_SkinPoint IGC models only
244 face_lipBotBase_00_M_SkinPoint IGC models only
245 face_lipTopBase_00_M_SkinPoint IGC models only
246 face_mouthCorner_00_L_SkinPoint IGC models only
247 face_mouthCorner_00_R_SkinPoint IGC models only
248 face_mouthCurlBot_00_M_SkinPoint IGC models only
249 face_mouthCurlTop_00_M_SkinPoint IGC models only
250 face_mouth_00_M_SkinPoint IGC models only
251 face_nasLab_00_L_SkinPoint IGC models only
252 face_nasLab_00_R_SkinPoint IGC models only
253 face_nasLab_01_L_SkinPoint IGC models only
254 face_nasLab_01_R_SkinPoint IGC models only
255 face_noseBase_00_M_SkinPoint IGC models only
256 face_sneerDriver_00_L_SkinPoint IGC models only
257 face_sneerDriver_00_R_SkinPoint IGC models only
258 face_sneerLower_00_L_SkinPoint IGC models only
259 face_sneerLower_00_R_SkinPoint IGC models only
260 face_sneer_00_L_SkinPoint IGC models only
261 face_sneer_00_R_SkinPoint IGC models only
262 face_teethBot_00_M_SkinPoint IGC models only
263 face_teethTop_00_M_SkinPoint IGC models only
264 face_tongue_00_M_SkinPoint IGC models only
265 root_main_00_M_SkinPoint IGC models only
266 spine_mainBendy_00_M_SkinPoint IGC models only
267 clavicle_main_00_L_SkinPoint IGC models only
268 arm_shoulderBendy_00_L_SkinPoint IGC models only
269 hand_main_00_L_JNT
270 hand_index_00_L_SkinPoint IGC models only
271 hand_main_00_L_SkinPoint IGC models only
272 hand_ring_00_L_SkinPoint IGC models only
273 hand_pinky_00_L_SkinPoint IGC models only
274 hand_thumb_00_L_SkinPoint IGC models only
275 clavicle_main_00_R_SkinPoint IGC models only
276 arm_shoulderBendy_00_R_SkinPoint IGC models only
277 hand_main_00_R_JNT
278 hand_main_00_R_SkinPoint IGC models only
279 hand_middle_00_R_SkinPoint IGC models only
280 hand_ring_00_R_SkinPoint IGC models only
281 hand_pinky_00_R_SkinPoint IGC models only
282 hand_thumb_00_R_SkinPoint IGC models only
283 head_main_00_M_SkinPoint IGC models only
284 face_jaw_00_M_SkinPoint IGC models only
285 EXP_L1_Eye1
286 EXP_R1_Eye1
287 EXP_L1_EyeLid1
288 EXP_R1_EyeLid1
289 EXP_L1_EyeLid2
290 EXP_R1_EyeLid2
292 EXP_L1_WingArm1Twist1
293 EXP_R1_WingArm1Twist1
296 waterfall_top_sound
297 waterfall_bottom_sound

Non-Key Bone Names

The below list is confirmed bone names (the CRC for these still appears in models), although they aren't used as key bones.

The non-key bone name table is collapsed by default to reduce page length. Click expand on the right to expand.

Name CRC
$BWA 0x892b044e
$BWP 0xe39b24bc
$BWR 0x0d954590
$BWS 0x7a927506
$CAH 0xedad630a
$CPP 0xad18d84c
$CSS 0x1f3cda35
$CST 0x81584f96
$DTH 0xdf57939b
$ESD 0x98622340
$FD1 0xbfc89c3c
$FL0 0x001626a2
$FR0 0xd457197d
$FSD 0x9a249d19
$HIT 0x3d205aac
$SCD 0xca0beed3
$SHL 0x2724bf2a
$SHR 0xdd2b8249
$TRD 0x9c9ddb46
-Blid_L01 0x952640dd
-EF_Eyelid01 0xff05e0fc
-EF_Eyelid_Death 0x91249ce5
-Eye01 0x4c50b066
-Flid_R01 0x07d4c05d
ankle_L 0x1114bd09
ankle_R 0xeb1b806a
arm_L 0xa100d5a4
arm_R 0x5b0fe8c7
B_Loin_01 0x46c1b68f
B_Loin_02 0xdfc8e735
Belly 0x49b52472
Blid_L 0xe42907dd
Blid_R 0x1e263abe
CalfL 0x57ebb63c
CalfR 0xade48b5f
CheekL 0x2acb54c0
CheekR 0xd0c469a3
ChestL 0xff3a859c
ChestR 0x0535b8ff
dSpine1_joint 0xfdba68c8
Ear_L_01 0xed8d18a0
Ear_L_02 0x7484491a
Ear_R_01 0x5d4b380c
Ear_R_02 0xc44269b6
elbow_L 0xa8948290
elbow_R 0x529bbff3
eye_L 0x8d92a5dd
eye_R 0x779d98be
EyeBowL 0x9b22dfb5
EyeBowR 0x612de2d6
EyebrowL 0x5bcd3a5c
EyebrowR 0xa1c2073f
F_Loin_01 0x1b2de783
F_Loin_02 0x8224b639
F_Loin_03 0xf52386af
fin 0xad2ef231
finger_L 0x6da04d3b
finger_R 0x97af7058
FinLeft_joint50 0x4eb9c4de
FinRight_joint51 0x9d4d0569
Flid_L 0x7fb845cb
Flid_R 0x85b778a8
foot_L 0x06f9c681
foot_R 0xfcf6fbe2
FootBackL 0xbd5cfe0e
FootBackR 0x4753c36d
FootFrontL 0xad3efd23
FootFrontR 0x5731c040
Geo_EyeLid_Death 0x35d090eb
GEO_EyelidL 0x428bb93e
GEO_EyelidR 0xb884845d
hand_L 0x2759dd85
hand_R 0xdd56e0e6
HeadScale_joint22 0x9b0a86ea
hip_L 0x3ff65768
hip_R 0xc5f96a0b
HipL 0x68b72b2c
HipR 0x92b8164f
Hips 0xded10611
Hump 0xad8861a3
IndexFingerTipL 0x76451c91
IndexFingerTipR 0x8c4a21f2
IndexL01 0x135d4e78
IndexL02 0x8a541fc2
IndexL03 0xfd532f54
IndexR01 0x05e5c002
IndexR02 0x9cec91b8
IndexR03 0xebeba12e
JawBottomL 0xa914ce26
JawBottomR 0x531bf345
JawTopL 0xa2fc75cc
JawTopR 0x58f348af
knee_L 0xf55ed5cc
knee_R 0x0f51e8af
KneeL 0x1b0485e7
KneeR 0xe10bb884
Leg01_Back_L 0x2755d694
Leg01_Back_R 0xdd5aebf7
Leg01_Front_R 0x76f67c58
Leg01_Middle_R 0x239aad94
Leg01Front_L 0x8b498ad5
Leg01Middle_L 0x9e20ef43
Leg02_Back_L 0xa9dad177
Leg02_Back_R 0x53d5ec14
Leg02_Front_R 0x4f7b409d
Leg02_Middle_L 0x32a22bf4
Leg02_Middle_R 0xc8ad1697
Leg02Front_L 0x05c68d36
Leg03_Back_L 0x6570d1e9
Leg03_Back_R 0x9f7fec8a
Leg03_Front_R 0x580054de
Leg03_Middle_L 0xdd6040ca
Leg03_Middle_R 0x276f7da9
Leg03Front_L 0xc96c8da8
LegBackL01 0x7e47654c
LegBackL02 0xe74e34f6
LegBackR01 0x68ffeb36
LegBackR02 0xf1f6ba8c
LegFrontL01 0xe0f912f0
LegFrontL02 0x79f0434a
LegFrontR01 0xf6419c8a
LegFrontR02 0x6f48cd30
LegL 0xeb4cc189
LegR 0x1143fcea
LFLeg_joint41 0x88363db0
LFLeg_joint42 0x113f6c0a
LFLeg_joint43 0x66385c9c
LFLeg_joint46 0x1652a813
LMLeg_joint41 0xf9a499be
LMLeg_joint42 0x60adc804
LMLeg_joint43 0x17aaf892
LMLeg_joint45 0xfec95da7
Lower_01 0x0955c6d0
Lower_02 0x905c976a
Lower_03 0xe75ba7fc
Lower_04 0x793f325f
Lower_05 0x0e3802c9
Lower_06 0x97315373
MiddleFingerTipL 0xc820b7d8
MiddleFingerTipR 0x322f8abb
Neck 0x8023796d
NoseLeft_joint18 0xbc61b1ae
Object26 0x29aadf6c
PinkyFingerTipL 0x33785489
PinkyFingerTipR 0xc97769ea
PinkyL01 0x1319a094
PinkyL02 0x8a10f12e
PinkyL03 0xfd17c1b8
PinkyR01 0x05a12eee
PinkyR02 0x9ca87f54
PinkyR03 0xebaf4fc2
Plane01 0x7ea01827
Plane02 0xe7a9499d
Plane03 0x90ae790b
Plane04 0x0ecaeca8
Plane05 0x79cddc3e
RFLeg_joint41 0xc6d797da
RFLeg_joint42 0x5fdec660
RFLeg_joint43 0x28d9f6f6
RFLeg_joint49 0xc80c1fe8
RingFingerTipL 0x46372b47
RingFingerTipR 0xbc381624
RLLeg_joint042 0x3c3468ef
RLLeg_joint043 0x4b335879
RLLeg_joint044 0xd557cdda
RLLeg_joint40 0x5be74f2d
RLLeg_joint41 0x2ce07fbb
RLLeg_joint42 0xb5e92e01
RLLeg_joint43 0xc2ee1e97
RLLeg_joint47 0xc583da8e
RMLeg_joint41 0xb74533d4
RMLeg_joint42 0x2e4c626e
RMLeg_joint43 0x594b52f8
RMLeg_joint48 0xce998b70
Spine1Sale_joint 0x65038b0e
Spine2_joint 0x76b94270
Spine2Scale_joint 0xff91da65
Spine3_joint 0xd0ce49c4
Spine3Scale_joint 0x6434960a
SpineLower 0x04672713
SpineUp 0x127d657f
SpineUpper 0x64a283ac
Tail01 0x0bf409bb
Tail02 0x92fd5801
Tail1_joint 0x425a6dd1
Tail2_joint 0x73b2774c
Tail3_joint 0xd5c57cf8
Tail3Scale_joint 0xb632d0ba
Tail4_joint 0x10624276
Taile4Scale_joint 0x991f686c
TailScale_joint 0x0e8b6be9
thumb_L 0x9088b57e
thumb_R 0x6a87881d
ThumbTipL 0x704d64e8
ThumbTipR 0x8a42598b
ToeBackL 0x83d2d151
ToeBackR 0x79ddec32
ToeFrontL 0x56d43f69
ToeFrontR 0xacdb020a
ToeL 0x414059ad
ToeR 0xbb4f64ce
Upper_01 0xe632304f
Upper_02 0x7f3b61f5
Upper_03 0x083c5163
Upper_04 0x9658c4c0
Upper_05 0xe15ff456
Upper_06 0x7856a5ec
WristL 0x07fbf58a

The following are unconfirmed names for non-keybones that were likely used in the past but are no longer used in any models today.

  • Geo_EyeglowR
  • Leg02Middle_L
  • Leg03Middle_L
  • NoseRight_joint39

Hierarchy requirement

from animkits (some 8.x+9.x build):

  • Full Body (-1 or 26 or 5)
    • Upper Body (4)
      • Right Shoulder (2)
        • Right Arm (1)
          • Right Hand (8 or 9 or 10 or 11 or 12)
      • Left Shoulder (3)
        • Left Arm (0)
          • Left Hand (13 or 14 or 15 or 16 or 17)
      • Head (6)
        • Jaw (7)
      • Face Upper (193) ≥ SL (9.0.1.34081)
      • Face Lower (228) ≥ SL (9.0.1.34081)
      • Hair (191) ≥ SL (9.0.1.34081)
      • Beard (192) ≥ SL (9.0.1.34081)
    • Wheel1…8 (27…34)

Geometry and rendering

Vertices

struct M2Vertex
{
  C3Vector pos;
  uint8 bone_weights[4];
  uint8 bone_indices[4];
  C3Vector normal;
  C2Vector tex_coords[2];  // two textures, depending on shader used
};

Models, too, use a Z-up coordinate systems, so in order to convert to Y-up, the X, Y, Z values become (X, -Z, Y).

-- Rour, the WoW vertex shaders all follow the same pattern, "Diffuse_XX_YY" (or sometimes XX, YY and Env). The particular vertex shader that is used chooses which set of texture coordinates to use. So Diffuse_T1 sends T1 texcoords to the fragment shader. Where Diffuse_T1_T2 sends both (for textures 0 and 1) but Diffuse_T1_T1 sends the same coords for both textures. Etc.

Views (LOD)

Skin profiles describe LOD views onto the model. They use all those lookup tables to be able to reference only parts of the lists while not being dynamically sized within the profile data.

≤ BC they were stored in the M2 itself, ≥ Wrath they have been moved to .skin files. The offsets are relative to the file the skin profile header is defined in. There is one .skin file per profile, each with a separate header, while in the inlined version, all headers are sequential. See the .skin file page for formats of both versions.

Render flags and blending modes

struct M2Material
{
  uint16_t flags;
  uint16_t blending_mode; // apparently a bitfield
} materials[];
  • Flags:
Flag Meaning
0x01 Unlit
0x02 Unfogged
0x04 Two-sided (no backface culling if set)
0x08 depthTest
0x10 depthWrite
0x40 shadow batch related ??? (seen in WoD) (seen in 31 models in Wrath)
0x80 shadow batch related ??? (seen in WoD) (seen in 2 models in Wrath : SpectralTiger.m2 and SpectralTigerEpic.m2)
0x100 (seen in 1 model in Wrath : HFjord_Fog_02.m2)
0x200 shadow batch related ??? (seen in WoD)
0x400 ??? (seen in WoD)
0x800 prevent alpha for custom elements. if set, use (fully) opaque or transparent. (litSphere, shadowMonk) (MoP+)
  • Blending mode

See the table at M2/Rendering#M2BLEND for a list of the current blend modes in the client.

Second Texture Material Override Combo

If this block is present (globalflags&8) the M2 will use multitexturing differently, the second texture will get the material to use from here (flags and blending mode) instead of current index material + 1, the Shader ID from the [Texture Unit] will tell what Second Texture Material to use (so, Shader ID will always be an even number), while materialIndex will still be used as the material for the first texture.

When multitexturing is applied, the second texture will get all the needed information from the first texture + 1, so, the texture to use will be the Index into Texture lookup table + 1, same for [Texture Mapping], TexAnim, etc.

According to wod, if the M2 Header has flag 0x08, instead of reading blend mode from M2 RenderFlags, blendMode is read from the raw blend maps referenced in header.

var flags = renderFlags[texUnit.renderFlags];
var blendMode = flags >> 16;
if ((header.GlobalModelFlags & 0x08) != 0 && texUnit.shader_id < mBlendMap.Length)
  blendMode = mBlendMap[texUnit.shader_id];

Texture mapping lookup table

This section only applies to versions < Cata. Still present but unused in Cataclysm.
struct 
{
  int16 unit; // -1, 0, or 1. see below
} tex_mapping_lookup_table[]; (alt. name: tex_unit_lookup_table, textureCoordCombos)
Value Description
-1 Environment mapping
0 First UVMap
1 Second UVMap

Referenced in the [Skin Texture Unit], defines the mapping to be used for textures. Typically, when there's no multitexturing, the value is set to 0, indicating the use of the first UVMap. However, it can also be set to 1 or -1, representing the second UVMap or environment mapping, respectively. For models that utilize multitexturing, the table also specifies the mappings to be used by both textures. It functions in pairs, like the [Texture Lookup Table]. The first texture will use the mapping corresponding to the provided ID, while the second texture will use the mapping associated with the next ID in sequence.

One model is of special interest, Creature/KelThuzad/KelThuzad.m2, which is the only one that has an nTexUnits of 3, and has three texture units specified for some of its submeshes. Sure enough, two of those map to 0 and 1, and one maps to -1.

More confusion thanks to my favorite "weird" model, World/Generic/Gnome/Passive Doodads/GnomeMachine/GnomeSubwayGlass.m2, which is the translucent, environment mapped glass tunnel in the Deeprun Tram. It only has a single value in this block, -1, which is used for the single texture layer in both render operations in the model. This and the magic with rendering flags/blend modes make up the neat transparent-reflective glass effect, but confuse me even more about how envmapping and such is handled. (and where it seems to get the bluish color from - is it in the model (no color blocks in this particular model), the wmo, a solid background color, or simply the result of the blending used?)

As a side note, on my (dated) system WoW does every texture unit in a single pass.

Colors and transparency

Colors

struct M2Color
{
  M2Track<C3Vector> color; // vertex colors in rgb order
  M2Track<fixed16> alpha; // 0 - transparent, 0x7FFF - opaque. Normaly NonInterp
} colors[];

This block is the M2 equivalent to the GEOA chunk in MDX files, it represents the vertex color and visibility animations for meshes. Referenced from the Texture Unit blocks in the *.skin. If a texunit belonging to a submesh has a value of -1 then the submesh doesnot use this block. Contains a separate timeline for transparency values. If no animation is used, the given value is constant.

Transparency

struct M2TextureWeight
{
  M2Track<fixed16> weight;
} textureWeights[];

Specifies global transparency values in addition to the values given in the Color block. I assume these are multiplied together eventually.

Transparency lookup table

struct 
{
  uint16_t transparency;
} transparency_lookup[];

Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.

Textures

  • Textures are defined globally in a list, additionally, a lookup table is given, referenced during rendering, to select textures.
struct M2Texture
{
  uint32_t type;          // see below
  uint32_t flags;         // see below
  M2Array<char> filename; // for non-hardcoded textures (type != 0), this still points to a zero-byte-only string.
} textures[];

Note that at least WoD (6.0.1.18179) relies on both

  • non-type-0 textures having a one-byte sized string that has a first byte of \0
  • the filename to contain a zero terminating character as it is reading them into an uninitialized buffer and passes that buffer on without the size given.

Thus, filename's size shall contain the zero byte, i.e. buffer size rather than string length.

Also note that this buffer is on the stack and sized the usual 0x108 bytes, so this is a n implicit texture filename length limit.

Note that at least SL (9.1.5.40944) expects M2Array filename to be zeroed in order to lookup BLP FileDataIDs from TXID chunk. Otherwise you get a green texture in the game.

Texture Types

Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!) For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name, the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:

Value Meaning
0 - NONE - -- Texture given in filename
1 TEX_COMPONENT_SKIN -- Skin -- Body + clothes
2 TEX_COMPONENT_OBJECT_SKIN -- Object Skin -- Item, Capes ("Item\ObjectComponents\Cape\*.blp")
3 TEX_COMPONENT_WEAPON_BLADE -- Weapon Blade -- Used on several models but not used in the client as far as I see. Armor Reflect?
4 TEX_COMPONENT_WEAPON_HANDLE -- Weapon Handle
5 TEX_COMPONENT_ENVIRONMENT -- (OBSOLETE) Environment (Please remove from source art)
6 TEX_COMPONENT_CHAR_HAIR -- Character Hair
7 TEX_COMPONENT_CHAR_FACIAL_HAIR -- (OBSOLETE) Character Facial Hair (Please remove from source art)
8 TEX_COMPONENT_SKIN_EXTRA -- Skin Extra
9 TEX_COMPONENT_UI_SKIN -- UI Skin -- Used on inventory art M2s (1): inventoryartgeometry.m2 and inventoryartgeometryold.m2
10 TEX_COMPONENT_TAUREN_MANE -- Character Misc // (OBSOLETE) Tauren Mane (Please remove from source art)
11 TEX_COMPONENT_MONSTER_1 -- Monster Skin 1 -- Skin for creatures or gameobjects #1
12 TEX_COMPONENT_MONSTER_2 -- Monster Skin 2 -- Skin for creatures or gameobjects #2
13 TEX_COMPONENT_MONSTER_3 -- Monster Skin 3 -- Skin for creatures or gameobjects #3
14 TEX_COMPONENT_ITEM_ICON -- Item Icon -- Used on inventory art M2s (2): ui-button.m2 and forcedbackpackitem.m2 (CSimpleModel_ReplaceIconTexture("texture"))
15 ≥ Cata Guild Background Color
16 ≥ Cata Guild Emblem Color
17 ≥ Cata Guild Border Color
18 ≥ Cata Guild Emblem
19 ≥ SL Character Eyes
20 ≥ SL Character Jewelry // Accessory
21 ≥ SL Character Secondary Skin
22 ≥ SL Character Secondary Hair
23 ≥ SL Character Secondary Armor
24 ≥ SL
25 Seen in DracthyrDragon.m2 (10.0.0+)
26 Seen in DracthyrDragon.m2 (10.0.0+)

Flags

Value Meaning
0x1 Texture wrap X
0x2 Texture wrap Y
0x1+0x2 Texture wrap X+Texture wrap Y

Texture lookup table

struct 
{
  uint16_t texture;
} texture_lookup[];

Replacable texture lookup

struct 
{
  uint16_t replacement;
} texture_replacements[];

A reverse lookup table for 'replaced' textures, mapping replacable ids to texture indices or -1. Only goes up to the maximum id used in the model.

Its strange, that HARDCODED is in the list, as a model can have more than one of course. Its just the last one written to the file.

Effects

Texture Transforms

  • This block contains definitions for texture animations, for example, flowing water or lava in some models. The keyframe values are used in the texture transform matrix.
struct M2TextureTransform
{
  M2Track<C3Vector> translation;
  M2Track<C4Quaternion> rotation;    // rotation center is texture center (0.5, 0.5)
  M2Track<C3Vector> scaling;
} textureTransforms[];

Seems like UV rotation in some models are made against (0.5, 0.5) point instead of (0, 0). At least it's the case for world\goober\g_scourgerunecirclecrystal.m2

So to get the proper UV rotation it would be necessary apply rotation this way:

  • Translate UV anim matrix to point (0.5, 0.5)
  • Apply rotation mat from quaternion
  • UV anim matrix to point (-0.5, -0.5)

Texture Transforms lookup table

struct 
{
  uint16_t anim_texture_id; // -1 for static
} anim_texture_lookup[];

Ribbon emitters

struct M2Ribbon
{
  uint32_t ribbonId;                  // Always (as I have seen): -1.
  uint32_t boneIndex;                 // A bone to attach to.
  C3Vector position;                 // And a position, relative to that bone.
  M2Array<uint16_t> textureIndices;   // into textures
  M2Array<uint16_t> materialIndices;  // into materials
  M2Track<C3Vector> colorTrack;      // An RGB multiple for the material.[1]
  M2Track<fixed16> alphaTrack;       // And an alpha value in a short, where: 0 - transparent, 0x7FFF - opaque.
  M2Track<float> heightAboveTrack;    // Above and Below – These fields define the width of a ribbon in units based on their offset from the origin.[1]
  M2Track<float> heightBelowTrack;    // do not set to same!
  float edgesPerSecond;               // this defines how smooth the ribbon is. A low value may produce a lot of edges. 
                                      // Edges/Sec – The number of quads generated.[1]
  float edgeLifetime;                 // the length aka Lifespan. in seconds 
                                      // Time in seconds that the quads stay around after being generated.[1]
  float gravity;                      // use arcsin(val) to get the emission angle in degree 
                                      // Can be positive or negative. Will cause the ribbon to sink or rise in the z axis over time.[1]
  uint16_t textureRows;               // tiles in texture
  uint16_t textureCols;               // Texture Rows and Cols – Allows an animating texture similar to BlizParticle. Set the number of rows and columns equal to the texture.[1]
  M2Track<uint16_t> texSlotTrack;     // Pick the index number of rows and columns, and animate this number to get a cycle.[1]
  M2Track<uchar> visibilityTrack;
#if ≥ Wrath                           // TODO: verify version
  int16_t priorityPlane;
  int8_t RibbonColorIndex;
  int8_t textureTransformLookupIndex; //Index into m2data.header.textureTransformCombos. Applied only if m2data.header.global_flags.flag_unk_0x20000 flag is set
#endif
} ribbons[];

Some models that contain ribbon emitters and are viewable in the game world are: Wisps in BFD, Al'ar the Phoenix in Tempest Keep and any other phoenix models and the energy trails in the COT (not the actual instance, but the entrance cave in Tanaris Desert). Other models with ribbon emitters are spells and effects.

Parameters from the MDL format that are probably in here somewhere: emission rate, rows, cols ...?

Particle emitters

This is partly wrong as hell! Do not rely on this block, at all. It might even be wrong for WotLK.

M2ParticleOld

struct M2ParticleOld {
/*0x00*/  uint32 particleId;                        // Always (as I have seen): -1.
/*0x04*/  uint32 flags;                             // See Below
/*0x08*/  C3Vector Position;                       // The position. Relative to the following bone.
/*0x14*/  uint16 bone;                              // The bone its attached to.
/*0x16*/  union
  {
    uint16 texture;                         // And the textures that are used. 
#if ≥ Cata
    struct                                  // For multi-textured particles actually three ids
    {
      uint16_t texture_0 : 5;
      uint16_t texture_1 : 5;
      uint16_t texture_2 : 5;
      uint16_t : 1;
    };
#endif
  };
/*0x18*/  M2Array<char> geometry_model_filename;    // if given, this emitter spawns models
/*0x20*/  M2Array<char> recursion_model_filename;   // if given, this emitter is an alias for the (maximum 4) emitters of the given model

#if >= 262 (late Burning Crusade)
/*0x28*/  uint8 blendingType;                       // A blending type for the particle. See Below
/*0x29*/  uint8 emitterType;                        // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone
/*0x2a*/  uint16 particleColorIndex;                // This one is used for ParticleColor.dbc. See below.
#else
/*0x28*/  uint16 blendingType;                      // A blending type for the particle. See Below
/*0x2a*/  uint16 emitterType;                       // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone
#endif
#if ≥ Cata
/*0x2c*/  fixed_point<uint8_t, 2, 5> multiTextureParamX[2];
#else
/*0x2c*/  uint8 particleType;                       // Found below.
/*0x2d*/  uint8 headorTail;                         // 0 - Head, 1 - Tail, 2 - Both
                                            // Head - The particle is a billboarded square quad;[1]
                                            // Tail – A tail particle is billboarded along the axis of motion and stretches in length based on speed;[1]
                                            // Both – Draws both heads and tails;[1]
#endif
  uint16 textureTileRotation;               // Rotation for the texture tile. (Values: -1,0,1) -- priorityPlane
  uint16 textureDimensions_rows;            // for tiled textures
  uint16 textureDimensions_columns;
  M2Track<float> emissionSpeed;             // Base velocity at which particles are emitted.
  M2Track<float> speedVariation;            // Random variation in particle emission speed. (range: 0 to 1)
  M2Track<float> verticalRange;             // longitude; Drifting away vertically. (range: 0 to pi) For plane generators, this is the maximum polar angle of the initial velocity; 
                                            // 0 makes the velocity straight up (+z). For sphere generators, this is the maximum elevation of the initial position; 
                                            // 0 makes the initial position entirely in the x-y plane (z=0).
  M2Track<float> horizontalRange;           // latitude; They can do it horizontally too! (range: 0 to 2*pi) For plane generators, this is the maximum azimuth angle of the initial velocity; 
                                            // 0 makes the velocity have no sideways (y-axis) component. 
                                            // For sphere generators, this is the maximum azimuth angle of the initial position.
  M2Track<float> gravity;                   // Not necessarily a float; see below.
  M2Track<float> lifespan;                  // Number of seconds each particle continues to be drawn after its creation.[1]
#if ≥ Wrath
  float lifespanVary;                       // An individual particle's lifespan is added to by lifespanVary * random(-1, 1)
#endif
  M2Track<float> emissionRate; 
#if ≥ Wrath
  float emissionRateVary;                   // This adds to the base emissionRate value the same way as lifespanVary. The random value is different every update.
#endif
  M2Track<float> emissionAreaLength;        // For plane generators, this is the width of the plane in the x-axis.
                                            // For sphere generators, this is the minimum radius.
  M2Track<float> emissionAreaWidth;         // For plane generators, this is the width of the plane in the y-axis.
                                            // For sphere generators, this is the maximum radius.
  M2Track<float> zSource;                   // When greater than 0, the initial velocity of the particle is (particle.position - C3Vector(0, 0, zSource)).Normalize()
#if ≥ Wrath
  FBlock<C3Vector> colorTrack;             // Most likely they all have 3 timestamps for {start, middle, end}.
  FBlock<fixed16> alphaTrack;
  FBlock<C2Vector> scaleTrack;
  C2Vector scaleVary;                      // A percentage amount to randomly vary the scale of each particle
  FBlock<uint16> headCellTrack;             // Some kind of intensity values seen: 0,16,17,32 (if set to different it will have high intensity)
  FBlock<uint16> tailCellTrack;
#else
  float midPoint;                           // middleTime; Middle point in lifespan (0 to 1). 
                                            // Time (parametric) Middle – The relative time of the middle key to the life.[1]
  CImVector[3] colorValues;                // Color, Start/Middle/End – Three values of BGRA Multiply for the particle.[1]
  float[3] scaleValues;                     // Scale, Start/Middle/End – Scale of the particle in units across.[1]
  uint16[3] lifespanUVAnim;                 // Lifespan Head UV Anim – Starting, middle and ending cell number for animating texture sequence of the first half of the Head particle’s life. Repeat will cause this animating texture to cycle.[1]
  uint16[3] decayUVAnim;                    // Decay Head UV Anim - Starting, middle and ending cell number for animating texture sequence of the second half of the Head particle’s life. Repeat will cause this animating texture to cycle.[1]
  int16[2] tailUVAnim;                      // Lifespan Tail UV Anim - Starting and ending cell number for animating texture sequence of the first half of the Tail particle’s life. Repeat will cause this animating texture to cycle.[1]
  int16[2] tailDecayUVAnim;                 // Decay Tail UV Anim - Starting and ending cell number for animating texture sequence of the second half of the Tail particle’s life. Repeat will cause this animating texture to cycle.[1]
#endif
  float tailLength;                         // A multiplier to the calculated tail particle length.[1]
  float twinkleSpeed;                       // twinkleFPS; has something to do with the spread
  float twinklePercent;                     // same mechanic as MDL twinkleOnOff but non-binary in 0.11.0
  CRange twinkleScale;                     // min, max
  float burstMultiplier;                    // ivelScale; requires (flags & 0x40)
  float drag;                               // For a non-zero values, instead of travelling linearly the particles seem to slow down sooner. Speed is multiplied by exp( -drag * t ).
#if ≥ Wrath
  float baseSpin;                           // Initial rotation of the particle quad
  float baseSpinVary;
  float spin;                               // Rotation of the particle quad per second
  float spinVary;
#else
  float spin;                               // 0.0 for none, 1.0 to rotate the particle 360 degrees throughout its lifetime.
#endif

  M2Box tumble;
  C3Vector windVector;
  float windTime;

  float followSpeed1;
  float followScale1;
  float followSpeed2;
  float followScale2;
  M2Array<C3Vector> splinePoints;                                  // Set only for spline praticle emitter. Contains array of points for spline
  M2Track<uchar> enabledIn;                 // (boolean) Appears to be used sparely now, probably there's a flag that links particles to animation sets where they are enabled.
} particles[];

Spin can be a float value greater or less one. Results look better if use it as a "phase shift": particle_rotate = randfloat(-sys->rotation * pi, sys->rotation * pi); --Igor

M2Particle (Cata+)

This section only applies to versions ≥ Cata.
  • Cata+ has multi texture support
using fp_6_9 = fixed_point<uint16_t, 6, 9>;
struct vector_2fp_6_9 { fp_6_9 x; fp_6_9 y; };
struct M2Particle
{
  M2ParticleOld old;
  vector_2fp_6_9 multiTextureParam0[2];
  vector_2fp_6_9 multiTextureParam1[2];
} particles[];

In addition to these two parameters, ParticleType and HeadOrTail got reused (as in replaced at their current position) as multiTextureParamX[2] where all arrays are one entry per additional texture.

I don't know if the previous meaning of the two parameters still exists, got moved, or was just never used to begin with. ParticleType appears to be implicit by having flags & 0x10100000 (→ multi texture), a model (→ model) or neither (→ default).--Schlumpf (talk) 23:47, 29 October 2015 (UTC)

Particle Flags

Value Description
0x1 Particles are affected by lighting;
0x2
0x4 On emission, particle orientation is affected by player orientation
0x8 Particles travel "up" in world space, rather than model.
0x10 Do not Trail
0x20 Unlightning
0x40 Use Burst Multiplier
0x80 Particles in Model Space (Causes animation of the particle emitter to be carried over to the particles.[1])
0x100
0x200 spawn position randomized in some way?
0x400 STYLE: Pinned Particles, their quad enlarges from their creation position to where they expand.
0x800
0x1000 XYQuad Particles. They align to XY axis facing Z axis direction. (Causes the particles to be a type of tail that orients to the XY grid.[1])
0x2000 clamp to ground; call CParticleEmitter2::ProjectParticle
0x4000
0x8000
0x10000 ChooseRandomTexture
0x20000 STYLE: "Outward" particles, most emitters have this and their particles move away from the origin, when they don't the particles start at origin+(speed*life) and move towards the origin.
0x40000 STYLE: unknown. In a large proportion of particles this seems to be simply the opposite of the above flag, but in some (e.g. voidgod.m2 or wingedlionmount.m2) both flags are true.
0x80000 If set, ScaleVary affects x and y independently; if not set, ScaleVary.x affects x and y uniformly, and ScaleVary.y is not used.
0x200000 Random FlipBookStart
0x400000 Ignores Distance (or 0x4000000?!, CMapObjDef::SetDoodadEmittersIgnoresDistance has this one)
0x800000 gravity values are compressed vectors instead of z-axis values (see Compressed Particle Gravity below)
0x1000000 bone generator = bone, not joint
0x4000000 do not throttle emission rate based on distance
0x10000000 Particle uses multi-texturing (could be one of the other WoD-specific flags), see multi-textured section.

--Barncastle (talk) A comparison of 0.10.0's MDX files to 0.11.0's and 1.12.1's M2 files indicates that the MDX PRE2 flags (≥0x8000) were probably unchanged during the switch from MDL to M2, albeit separated into their own enum.

ParticleColorIndex

This is used in conjunction with ParticleColor.dbc to alter the default colour of particles. If the particle colour is not meant to be changed then its ParticleColorIndex will have a value of zero. If the particle colour may be changed then the value will be 11, 12 or 13, indicating whether the first, second or third Start, Mid and End colours are to be used, respectively. The row of ParticleColor.dbc to be used is determined its ID value, which should correspond to the ParticleColorID value supplied by CreatureDisplayInfo.dbc or ItemDisplayInfo.dbc for creatures or items.

Particle types

Value Description
0 "normal" particle
1 large quad from the particle's origin to its position (used in Moonwell water effects)
2 seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)

ParticleType is always 0 and, maybe, now (Flags & 0x40000) != 0 means "particles from origin to position". --Igor Checked and verified --BlinkHawk

Particle Blendings

Value Description
0 glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST);
1 glBlendFunc(GL_SRC_COLOR, GL_ONE);
2 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3 glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST);
4 glBlendFunc(GL_SRC_ALPHA, GL_ONE);

from Modelviewer source -- Rour, some WoD particle effects are using blend mode 0x7 here.

The Fake-AnimationBlock

  • Its pretty much like the real one but without the "header".
Offset Type Name Description
0x000 uint32 nTimestamps The number of timestamps.
0x004 uint32 ofsTimestamps And the offset to them. The timestamps are shorts! (?)
0x008 uint32 nKeys The same number again. This time its the number of Keys / Values.
0x00C uint32 ofsKeys And their offset.

But they're unable to change between different animations, so they directly point to the data.

Compressed Particle Gravity

Key values in the gravity track are decompressed at load time from a 4-byte value to a C3Vector.

struct CompressedParticleGravity {
  int8_t x, y;
  int16_t z;
};
for (/* each 4-byte value in the particle gravity track */) {
  float *pValue;
  C3Vector *pDst;
  if (particle.flags & 0x800000) {
    // interpret the 4 bytes at pValue as CompressedParticleGravity:
    CompressedParticleGravity v = new (pValue) CompressedParticleGravity();
    C3Vector dir = C3Vector(v.x, v.y, 0) * (1.0f / 128.0f);
    float z = sqrtf(1.0f - dir.Dot(dir));
    float mag = v.z * 0.04238648f;
    if (mag < 0) {
      z = -z;
      mag = -mag;
    }
    dir.z = z;
    dir *= mag;
    *pDst = dir;
  } else {
    *pDst = C3Vector(0, 0, -(*pValue));
  }
}


M2Box

struct M2Box {
  C3Vector ModelRotationSpeedMin;
  C3Vector ModelRotationSpeedMax;
}

Miscellaneous

Name

char name[];

Informative name used for debugging purposes. Not used in retail clients.

Bounding volumes

These blocks give a simplified bounding volume for the model. Characters and creatures have just a simple box.

Vertices

This block defines the possible points used for the model. They are referenced in the triangles block later.

struct 
{
  C3Vector position;
} bounding_vertices[];

Triangles

The number in the header tells us how many uint16s there are, not how many triangles. To use this better, you should group three of them into an array. The nBoundingTriangles/3 indices will tell you which vertices are used for the triangle then.

struct 
{
  uint16_t index; // three entries pointing to vertices per triangle
} bounding_triangles[];

The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles.

Normals

This one defines a normal per triangle. The vectors are normalized, but Blizzard seems to have some problems getting a simple vector normalized leading in several 0,0,0.999999999 ones. Whatever.

As each vertex has a corresponding normal vector, it should be true that nBoundingNormals = nBoundingTriangles / 3.

struct 
{
  C3Vector normal;
} bounding_normals[];

Lights

struct M2Light
{
/*0x00*/  uint16_t type;                      // Types are listed below.
/*0x02*/  int16_t bone;                       // -1 if not attached to a bone
/*0x04*/  C3Vector position;                 // relative to bone, if given
/*0x10*/  M2Track<C3Vector> ambient_color;
/*0x24*/  M2Track<float> ambient_intensity;   // defaults to 1.0
/*0x38*/  M2Track<C3Vector> diffuse_color;
/*0x4C*/  M2Track<float> diffuse_intensity;   // defaults to 1.0
/*0x60*/  M2Track<float> attenuation_start;
/*0x74*/  M2Track<float> attenuation_end;
/*0x88*/  M2Track<uint8_t> visibility;        // enabled?
/*0x9C*/
} lights[];

Two light types:

Value Description
0 Directional
1 Point light

Directional light type is not used (at least in 3.3.5) outside login screen, and doesn't seem to be taken into account in world.

In burning crusade (and maybe more versions) the glues models hardcode constant attenuation to 0, linear attenuation to 0.7 and quadratic attenuation to 0.03

Cameras

These blocks are present in the "flyby" camera models which completely lack geometry and the main menu backdrop models which are supposed to have a fixed camera. Additionally, characters and monsters also have this block. The reason that non-mainmenu and non-geometry M2s have cameras was is you can see the unit's portrait and the character info tab.

struct M2Camera
{
  uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table.
#if < Cata
  float fov; // Diagonal FOV in radians. See below for conversion.
#endif
  float far_clip;
  float near_clip;
  M2Track<M2SplineKey<C3Vector>> positions; // How the camera's position moves. Should be 3*3 floats.
  C3Vector position_base;
  M2Track<M2SplineKey<C3Vector>> target_position; // How the target moves. Should be 3*3 floats.
  C3Vector target_position_base;
  M2Track<M2SplineKey<float>> roll; // The camera can have some roll-effect. Its 0 to 2*Pi. 
#if ≥ Cata
  M2Track<M2SplineKey<float>> FoV; //Diagonal FOV in radians. See below for conversion.
#endif
} cameras[];

Camera field of view

The fov included in M2Camera is a diagonal field of view (in radians). The client converts it to a vertical field of view at runtime using the following formula:

float vfov = dfov / sqrt(1.0 + pow(aspect, 2.0));

The aspect ratio used is determined by the rect being presented on (eg. the game window).

Note that this formula makes an assumption that the screen being projected to is planar (ie a monitor).

Camera lookup table

This block lists the different cameras existing in the model. The index in the array is also the type. CameraLookupTable[1] is always the character tab camera.

"-1" type cameras are not referenced.

If nCameras >= 1, then nCameraLookup will be >= 1 regardless of whether any camera will be actually referenced in it. See interface/glues/models/ui_mainmenu_warlords/ui_mainmenu_warlords.m2. A valid block thus may be -1s only. This appears to be an exporter-quirk rather than a requirement.

struct
{
  uint16_t camera;
} camera_lookup[];

Attachments

This block specifies a bunch of locations on the body - hands, shoulders, head, back, knees etc. It is used to put items on a character. This seems very likely as this block also contains positions for sheathed weapons, a shield, etc.

struct M2Attachment
{
  uint32_t id;                      // Referenced in the lookup-block below.
  uint16_t bone;                    // attachment base
  uint16_t unknown;                 // see BogBeast.m2 in vanilla for a model having values here
  C3Vector position;               // relative to bone; Often this value is the same as bone's pivot point 
  M2Track<uchar> animate_attached;  // whether or not the attached model is animated when this model is. only a bool is used. default is true.
} attachments[];

Meaning depends on type of model. The following are for creatures/characters mainly:

ID Description ID Description ID Description ID Description ID Description
0 Shield / MountMain / ItemVisual0 12 Back 24 Special2 36 Bullet (version: somewhen after alpha) 48 RightFoot
1 HandRight / ItemVisual1 13 ShoulderFlapRight 25 Special3 37 SpellHandOmni (version: somewhen after alpha) 49 ShieldNoGlove
2 HandLeft / ItemVisual2 14 ShoulderFlapLeft 26 SheathMainHand 38 SpellHandDirected (version: somewhen after alpha) 50 SpineLow
3 ElbowRight / ItemVisual3 15 ChestBloodFront 27 SheathOffHand 39 VehicleSeat1 ≥ Wrath 51 AlteredShoulderR
4 ElbowLeft / ItemVisual4 16 ChestBloodBack 28 SheathShield 40 VehicleSeat2 ≥ Wrath 52 AlteredShoulderL
5 ShoulderRight 17 Breath 29 PlayerNameMounted 41 VehicleSeat3 ≥ Wrath 53 BeltBuckle ≥ Mists
6 ShoulderLeft 18 PlayerName 30 LargeWeaponLeft 42 VehicleSeat4 ≥ Wrath 54 SheathCrossbow
7 KneeRight 19 Base 31 LargeWeaponRight 43 VehicleSeat5 ≥ Wrath 55 HeadTop ≥ Legion
8 KneeLeft 20 Head 32 HipWeaponLeft 44 VehicleSeat6 ≥ Wrath 56 VirtualSpellDirected ≥ Battle
9 HipRight 21 SpellLeftHand 33 HipWeaponRight 45 VehicleSeat7 ≥ Wrath 57 Backpack ≥ Battle
10 HipLeft 22 SpellRightHand 34 Chest 46 VehicleSeat8 ≥ Wrath 60 Unknown ≥ Battle
11 Helm 23 Special1 35 HandArrow 47 LeftFoot

For weapons, usually 5 of these points are present, which correspond to the 5 columns in ItemVisuals.dbc, which in turn has 5 models from ItemVisualEffects.dbc. This is for the weapon glowy effects and such. The effect ID is the last column in ItemDisplayInfo.dbc. They take the ids 0 to 4. Mounts take the id 0 for their rider. Breath (17) is used by CGCamera::FinishLoadingTarget() aswell as some other one. The name above the head of a Unit (CGUnit_C::GetNamePosition) looks for PlayerNameMounted (29), then PlayerName (18).

Attachment Lookup

The index of the array defines, which type that attachment is of. Its the same as the list above. The lookups and the id of the animations point in a circle.

struct 
{
  uint16_t attachment;
} attachment_lookup[];

Events

These events are used for timing sounds for example. You can find the $DTH (death) event on nearly every model. It will play the death sound for the unit.

The events you can use depend on the way, the model is used. Dynamic objects can shake the camera, doodads shouldn't. Units can do a lot more than other objects.

Somehow there are some entries, that don't use the $... names but identifiers like "DEST" (destination), "POIN" (point) or "WHEE" (wheel). How they are used? Idk.

struct M2Event
{
  uint32_t identifier;  // mostly a 3 character name prefixed with '$'.
  uint32_t data;        // This data is passed when the event is fired. 
  uint32_t bone;        // Somewhere it has to be attached.
  C3Vector position;   // Relative to that bone of course, animated. Pivot without animating.
  M2TrackBase enabled;  // This is a timestamp-only animation block. It is built up the same as a normal AnimationBlocks, but is missing values, as every timestamp is an implicit "fire now".
} events[];

Possible Events

There are a lot more of them. I did not list all up to now.

Bow AnimEvents include $BWA, $BWP, $BWR, $BWS (If present, all of these should be childed to the right hand).

ID data what Type seen to be fired on Description
$AH[0-3] PlaySoundKit (customAttack[x]) soundEffect ID is defined by CreatureSoundDataRec::m_customAttack[x]
$BMD BowMissleDestination RangedWeapon Bow Middle
$AIM Vehicles CGUnit_C::ComputeMissileTrajectory Position used as MissileFirePos.
$ALT anim_swap_event / DisplayTransition Unit CUnitDisplayTransition_C::UpdateState(1) or CUnitDisplayTransition_C::HandleAnimSwapEvent
$BL[0-3] FootstepAnimEventHit (left) Unit Backwards
$BR[0-3] FootstepAnimEventHit (right) Unit Backwards
$BRT PlaySoundKit (birth) soundEffect ID is defined by CreatureSoundDatarec::m_birthSoundID
$BTH Breath Unit All situations, where nothing happens or breathing. Adds Special Unit Effect based on unit state (under water, in-snow, …)
$BWP PlayRangedItemPull (Bow Pull) Unit LoadRifle, LoadBow
$BWR BowRelease Unit AttackRifle, AttackBow, AttackThrown
$CAH Unit Attack*, *Unarmed, ShieldBash, Special* attack hold? CGUnit_C::HandleCombatAnimEvent
$CCH Unit mostly not fired, AttackThrown Fishing Pole CEffect::DrawFishingString needs this on the model for getting the string attachments.
$CFM Unit CGCamera::UpdateMountHeightOrOffset CGCamera::UpdateMountHeightOrOffset: Only z is used. Non-animated. Not used if $CMA
$CHD Unit not fired probably does not exist?!
$CMA Unit CGCamera::UpdateMountHeightOrOffset: Position for camera
$CPP PlayCombatActionAnimKit parry, anims, depending on some state, also some callback which might do more
$CSD soundEntryId PlayEmoteSound Unit Emote*
$CSL release_missiles_on_next_update if has_pending_missiles (left) Unit AttackRifle, SpellCast*, ChannelCast* "x is {L or R} (""Left/right hand"") (?)"
$CSR release_missiles_on_next_update if has_pending_missiles (right) Unit AttackBow, AttackRifle, AttackThrown, SpellCast*, ChannelCast* "x is {L or R} (""Left/right hand"") (?)"
$CSS PlayWeaponSwooshSound sound played depends on CGUnit_C::GetWeaponSwingType
$CST release_missiles_on_next_update if has_pending_missiles Unit Attack*, *Unarmed, ShieldBash, Special*, SpellCast, Parry*, EmoteEat, EmoteRoar, Kick, ... $CSL/R/T are also used in CGUnit_C::ComputeDefaultMissileFirePos.
$CVS  ? Data: SoundEntriesAdvanced.dbc, Sound — Not present in 6.0.1.18179
$DSE DestroyEmitter MapObj
$DSL soundEntryId GO
$DSO soundEntryId DoodadSoundOneShot GO
$DTH DeathThud + LootEffect Unit Death, Drown, Knockdown """I'm dead now!"", UnitCombat_C, this plays death sounds and more." Note that this is NOT triggering CreatureSoundDataRec::m_soundDeathID, but that is just always triggered as soon as the death animation plays.
$EAC object package state enter 3, exit 2, 4, 5
$EDC object package state enter 5, exit 3, 4, 2
$EMV object package state enter 4, exit 3, 2, 5
$ESD PlayEmoteStateSound Unit soundEffect ID is implicit by currently played emote
$EWT object package state enter 2, exit 3, 4, 5
$FD[1-9] PlayFidgetSound CreatureSoundDataRec::m_soundFidget (only has 5 entries, so don’t use 6-9)
$FDX PlayUnitSound (stand) soundEffect ID is defined by CreatureSoundDataRec::m_soundStandID
$FL[0-3] FootstepAnimEventHit (left) Foot Left Forward
$FR[0-3] FootstepAnimEventHit (right) Foot Right Forward
$FSD HandleFootfallAnimEvent Unit Walk, Run (multiple times), ... Plays some sound. Footstep? Also seen at several emotes etc. where feet are moved. CGUnit_C::HandleFootfallAnimEvent
$GC[0-3] GameObject_C_PlayAnimatedSound soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x + 6] ({Custom0, Custom1, Custom2, Custom3})
$GO[0-5] GameObject_C_PlayAnimatedSound soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x] ({Stand, Open, Loop, Close, Destroy, Opened})
$HIT PlayWoundAnimKit Unit Attack*, *Unarmed, ShieldBash, Special* soundEntryId depends on SpellVisualKit
$KVS  ? MapLoad.cpp -- not found in 6.0.1.18179
$RL[0-3] FootstepAnimEventHit (left) Running
$RR[0-3] FootstepAnimEventHit (right) Running
$SCD PlaySoundKit (spellCastDirectedSound) soundEffect ID is defined by CreatureSoundDataRec::m_spellCastDirectedSoundID
$SHK spellEffectCameraShakesID AddShake GO
$SHL ExchangeSheathedWeapon (left) Sheath, HipSheath
$SHR ExchangeSheathedWeapon (right) Sheath, HipSheath
$SL[0-3] FootstepAnimEventHit (left) Stop, (JumpEnd), (Shuffle*) Stop
$SMD PlaySoundKit (submerged) soundEffect ID is defined by CreatureSoundDatarec::m_submergedSoundID
$SMG PlaySoundKit (submerge) soundEffect ID is defined by CreatureSoundDatarec::m_submergeSoundID
$SND soundEntryId PlaySoundKit (custom) GO
$SR[0-3] FootstepAnimEventHit (right) Stop, (JumpEnd), (Shuffle*) Stop
$STx Mounts MountTransitionObject::UpdateCharacterData Not seen in 6.0.1.18179 -- x is {E and B} , sequence time is taken of both, pivot of $STB. (Also, attachment info for attachment 0)
$TRD HandleSpellEventSound Unit EmoteWork*, UseStanding* soundEffect ID is implicit by SpellRec
$VG[0-8] HandleBoneAnimGrabEvent
$VT[0-8] HandleBoneAnimThrowEvent
$WGG PlayUnitSound (wingGlide) soundEffect ID is defined by CreatureSoundDataRec::m_soundWingGlideID
$WL[0-3] FootstepAnimEventHit (left)
$WLB Model Weapon Left Bot
$WLT Model Weapon Left Top
$WNG PlayUnitSound (wingFlap) soundEffect ID is defined by CreatureSoundDataRec::m_soundWingFlapID
$WR[0-3] FootstepAnimEventHit (right)
$WRB Model Weapon Right Bot
$WRT Model Weapon Right Top
$WTB Weapons Bow Bottom, Weapon Trail Bottom position, also used for Bow String
$WTT Weapons Bow Top, Weapon Trail Top position
$WWG  ? Calls some function in the Object VMT. -- Not seen in 6.0.1.18179
DEST  ? exploding ballista, that one has a really fucked up block. Oo
POIN Unit not fired Data: ?, seen on multiple models. Basilisk for example. (6801)
WHEE  ? Data: 601+, Used on wheels at vehicles.
BOTT  ? Purpose unknown. Seen in well_vortex01.m2
TOP  ? Purpose unknown. Seen in well_vortex01.m2

References

1. Warcraft 3 Art Tools - http://ftp.blizzard.com/pub/war3/other/WarcraftIIIArtTools1.01.zip