https://wowdev.wiki/api.php?action=feedcontributions&user=Zee&feedformat=atomwowdev - User contributions [en]2024-03-29T07:47:53ZUser contributionsMediaWiki 1.39.7https://wowdev.wiki/index.php?title=WMO&diff=34942WMO2022-07-26T16:19:55Z<p>Zee: /* MGI2 */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CImVector}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.1.0.39015}}<br />
<br />
Extension to MOLT. Present in file 3623016 (9.1 Broker Dungeon). Not to be confused with the old 0.5.3 MOLV chunk.<br />
<br />
struct {<br />
/*0x00*/ struct {<br />
/*0x00*/ {{Type|C3Vector}} direction; // usually either xy or z and the remainder 0.<br />
/*0x0C*/ float value;<br />
/*0x10*/ } _0x00[6];<br />
/*0x60*/ byte _0x60[3];<br />
/*0x63*/ uint8_t molt_index; // multiple MOLV may reference/extend the same MOLT.<br />
/*0x64*/<br />
} mapObjectLightV[];<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodad_ID[]; // should be same count as SMOHeader.nDoodadNames in [[WMO#MOHD_chunk|MOHD]] chunk<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence. Note that if MODN is used it is a byte offset to the beginning of filepath 0-terminated string, for MODI it is an index.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1; // If set, the doodad can have textures projected onto it, e.g. the npc selection circle<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called). When enabled uses wmo interior lighting function, when disabled uses exterior lighting<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
// when A is != 0xff && A < 255, A is a MOLT index and that's used instead the RGB given here, taking distance and intensity into account<br />
// If A > MOLT count, then MOLT[0] is used<br />
// If A == 255, the shading direction vector is based on the center of the group and not the sun direction vector, the look-at vector from group bounds center to doodad position<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MDDI {<br />
char unk[4]; // probably float<br />
} something[nDoodads];<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
/*0x00*/ uint16_t unk0;<br />
/*0x02*/ char unk1[0xE];<br />
/*0x10*/<br />
} m_fog_extra_data[shall be same as MFOG count];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ SMOGroupFlags2 flags2; // a copy of the flags2 that is present in the group file as well<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
*'''These are a new type of dynamic lights added in Shadowlands. E.g. Castle Nathria raid has 833 of them.'''<br />
*'''They're used for everything from torch fires to projecting light/shadow on the ground to make it look like light is coming through a window.'''<br />
<br />
struct MNLD {<br />
int type; // 0 = Point light (sphere), 1 = Spot light (cone) <br />
int lightIndex; // Appears to be same as index in mapobject_new_light_defs[]<br />
int enableColorGradient; // 0 = false (use only startColor), 1 = true (use start and end color gradient)<br />
int indexRelated2; // Into other struct // possibly for layering lights<br />
{{Template:Type|CImVector}} startColor; // Start Color<br />
{{Template:Type|C3Vector}} position; // Light position in WMO<br />
{{Template:Type|C3Vector}} rotation; // Euler rotation in radians, for spot light rotates the light, for point light rotates the light cookie<br />
float attenStart; // Start attenuation<br />
float attenEnd; // End attenuation<br />
float intensity; // Light intensity<br />
{{Template:Type|CImVector}} endColor; // End Color<br />
float colorBlendStart; // Gradient start distance from emitter position, for mixing start and end color<br />
float colorBlendEnd; // Gradient end distance from emitter position, for mixing start and end color<br />
char gap0[4]; // empty<br />
float flickerIntensity; // Flickering light intensity<br />
float flickerSpeed; // Flickering light speed<br />
int flickerMode; // 0 = off, 1 = sine curve, 2 = noise curve, 3 = noise step curve<br />
{{Template:Type|C3Vector}} field_54; // Only found 0's so far<br />
char gap1[4]; // empty<br />
uint lightCookieFileID; // file ID for light cookie texture. For point light it's a cube map<br />
char gap2[20]; // empty<br />
float spotlightRadius; // The overall radius of the spot light, in radians<br />
float spotlightDropoffStart; // Start of drop-off gradient, in radians. Starts at center, ends at edge. Controls the rate at which light intensity decreases from the center to the edge of the spot light beam<br />
float spotlightDropoffEnd; // End of drop-off gradient, in radians. Both start and end drop-off angles have to be smaller than radius else sharp edge<br />
uint unk0; // 14336 (power of 2)<br />
char gap4[41]; // empty<br />
char field_50; // Only found 0's so far<br />
char unk1[2]; // Only found 0's so far<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct {<br />
/*0x00*/ float _0x0; // can apparently be overwritten by groups,<br />
// potentially inside MOBA's unknown reuse blob.<br />
// used in determining locs based on moba vertices.<br />
// minimum triangle area?<br />
/*0x04*/ uint16_t layerCount;<br />
/*0x06*/ Layer detailDoodadLayers[layerCount]; // global<br />
/*0x??*/ GroupData groupData[until-end-of-chunk]; // per WMO group<br />
/*0x??*/<br />
};<br />
<br />
struct Layer {<br />
/*0x00*/ uint8_t density?; // if density? < (v14 >> 20) - 24 * (v14 / 25165824), have no<br />
// doodad on location, where v14 is a random number based on <br />
// a seed based on the vertex index<br />
<br />
/*0x01*/ uint8_t detailDoodadsCount;<br />
/*0x02*/ DetailDoodad detailDoodads[detailDoodadsCount];<br />
/*0x??*/<br />
};<br />
struct DetailDoodad {<br />
/*0x00*/ {{Type/foreign_key|table=GroundEffectDoodad}} doodad;<br />
/*0x04*/ uint8_t weight; // not required to accumulate to something specific.<br />
/*0x05*/<br />
};<br />
<br />
struct GroupData {<br />
/*0x00*/ uint16_t groupIndex;<br />
/*0x02*/ uint32_t dataSize;<br />
/*0x06*/ char data[dataSize]; // interpreted when parsing groups as per parse_group_data() below<br />
/*0x??*/<br />
};<br />
<br />
void parse_group_data() {<br />
restart_layer:<br />
uint16_t layer_index = read_uint16_t(); // index into detailDoodadLayers<br />
if (layer_index == 0xFFFF) {<br />
return;<br />
}<br />
<br />
restart_batch:<br />
uint16_t batch_index = read_uint16_t(); // batch as in MOBA<br />
if (batch_index == 0xFFFF) {<br />
goto restart_layer;<br />
}<br />
else if (batch_index & 0x8000) {<br />
// roll for locs of all loc_ranges of this batch<br />
goto restart_batch;<br />
}<br />
<br />
int locrange_index = 0; // loc as in triangles that satisfy some criteria<br />
// for all batches in batch order<br />
restart_locrange_index_part:<br />
uint8_t locrange_index_part = read_uint8_t();<br />
locrange_index += locrange_index_part & 0x7F;<br />
if (locrange_index_part == 0xFF) {<br />
goto restart_batch;<br />
}<br />
else if (locrange_index_part == 0x7F) { // RLE for an integer?!<br />
goto restart_locrange_index_part;<br />
}<br />
bool single_loc = locrange_index_part & 0x80;<br />
<br />
int loc = 0;<br />
restart_loc_part:<br />
uint8_t loc_part = read_uint8_t();<br />
loc += loc_part;<br />
if (loc_part == 0xFF) {<br />
goto restart_locrange_index_part; /// not in front of that, i.e. resetting to 0?!<br />
}<br />
else if (loc_part == 0xFE) { // RLE for an integer?!, yes different sentinel!<br />
goto restart_loc_part;<br />
}<br />
<br />
// take loc range for loc_range_index<br />
// if single_loc, roll once for locs[loc_range.begin + loc]<br />
// else, roll for locs[loc_range.begin + 0...loc]<br />
<br />
goto restart_batch;<br />
}<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
/*0x3C*/ SMOGroupFlags2 flags2;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=9|max_build=9.1.5}} <br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#else<br />
/*0x40*/ int16_t parentOrFirstChildSplitGroupIndex; //See [[WMO#Split_Groups|Split Groups]]<br />
/*0x42*/ int16_t nextSplitChildGroupIndex; <br />
#endif<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled. If camera is AABB present in a group with this flag, and not present in any group with SMOGroup::INTERIOR, render all exteriors<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). If group has SMOGroup::INTERIOR flag and this flag then exterior lighting is used for the group)<br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups). Seems to disable shadow casting when on<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE. When set seems to disable rendering of batches, but still renders doodads<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
===group flags 2===<br />
<br />
struct SMOGroupFlags2 {<br />
//0x1<br />
uint32_t canCutTerrain : 1; = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
//0x2<br />
uint32_t unk2 : 1;<br />
//0x4<br />
uint32_t unk4 : 1;<br />
//0x8<br />
uint32_t unk8 : 1;<br />
//0x10<br />
uint32_t unk0x10 : 1;<br />
//0x20<br />
uint32_t unk0x20 : 1;<br />
//0x40<br />
uint32_t isSplitGroupParent : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=9}} since around 9.2.0<br />
//0x80<br />
uint32_t isSplitGroupChild : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=9}} since around 9.2.0<br />
};<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
===Split Groups===<br />
<br />
First spotted in 9.2.0 Split groups is a new mechanic, which makes parent-child relation between Group WMOs. <br />
There is "Parent Split" Group and "Child Split" Groups, that be belong to Parent. From the way the data is organized right now, it's not possible for a "Child Split" Group to be a "Parent Split" for other Group WMOs. So this graph can have only one level of depth.<br />
<br />
<br />
Essentially "Child Split" Group do not have Portals leading to them. Instead all portals leading in and out are connected to "Parent Split". So portal culling algorithm needs to render "Child Split" Groups, when "Parent Split" passes Portal culling test.<br />
<br />
<br />
How it works:<br />
<br />
<br />
if <code>SMOGroupFlags2.isSplitGroupParent</code> flag is set, the group is marked as "Parent Split" Group. In this case <code>MOGP.parentOrFirstChildSplitGroupIndex</code> is a group index of first "Split Child" Group and <code>MOGP.nextSplitChildGroupIndex</code> is set to -1 (at least as of current observations).<br />
<br />
<br />
For "Split Child" <code>SMOGroupFlags2.isSplitGroupParent</code> is not set and <code>SMOGroupFlags2.isSplitGroupChild</code> is set instead. In this case <code>MOGP.parentOrFirstChildSplitGroupIndex</code> is index of "Parent Split" Group to which this "Split Child" belongs to, and <code>MOGP.nextSplitChildGroupIndex</code> is group index of next "Split Child" in the list.<br />
<br />
<br />
So to get all "Split Child" for particular "Parent Split", one would need to take <code>MOGP.parentOrFirstChildSplitGroupIndex</code> of "Parent Split" and go through chain of referencing <code>MOGP.nextSplitChildGroupIndex</code>, while <code>MOGP.nextSplitChildGroupIndex</code> is not -1<br />
<br />
<br />
For earliest known example of WMO with such mechanic, see FDID 4217818<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
''('''M'''ap'''O'''bject '''V'''ertex '''I'''ndices)''<br />
<br />
The group's vertex indices from the group's vertex list (MOVT, MONR, MOTV) to form triangles. <br />
<br />
uint16[] Indices;<br />
<br />
Three indices form a single triangle. Therefore, the number of indices should be divisible by 3. <br />
<br />
All triangles are set in a right-handed coordinate system, which means the order of vertices is anti-clockwise to make a front-face triangle (positive area). When used in a left-handed coordinate system, the 2nd and 3rd vertex indices of each triangle have to be swapped, otherwise these triangles form a negative area, and with back-side culling enabled, get culled. When incorrectly set, a 3D renderer will make textured meshes look "inside-out".<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9}}<br />
Previous versions of the game are forgiving to cases when there are OxFF collision only invisible triangles between minIndex and maxIndex. In Shadowlands having these inside the batches will result into various rendering glitches on some machines that support bindless texturing.<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
There was a common misconception that this BSP is only used for collision purposes. In fact, it is also used by the client to determine if you are currently inside the WMO group (empirically observed). <br />
So, if any faces are missing from the BSP, indoor groups will be culled on approaching them. For outdoor groups they seem to just not have collision.<br />
This, in turn, debunks another common misconception that WMOs use two separate collision systems - the BSP one and unbatched geometry marked with material ID 0xFF and collision flag in MOPY.<br />
In practice, ommiting those collision faces from BSP also yields incorrect results, resulting into the absence of collision for those faces as well as culling issues for indoor groups. --[[User:Skarn|Skarn]] ([[User talk:Skarn|talk]]) 15:56, 17 April 2022 (CEST)<br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t legacyLiquidType : 4; // For older WMOs, used to set liquid type. <br />
uint8_t unknown1 : 1;<br />
uint8_t unknown2 : 1;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z.<br />
This seems to only happen if MOHD.flags.use_liquid_type_dbc_id is set<br />
<br />
In older WMOs without the MOHD root flag flag_use_liquid_type_dbc_id set : if MOGP.groupLiquid == 15 (green lava), the tile flags legacyLiquidType are used to set the liquid type.<br />
First it checks if legacyLiquidType <= 20. If so : <br />
- if legacyLiquidType == 1 : Liquidtype = 14 (Ocean)<br />
- if legacyLiquidType == 2 : Liquidtype = 19 (WMO Magma)<br />
- if legacyLiquidType == 3 : Liquidtype = 20 (WMO Slime)<br />
- if legacyLiquidType >= 4 : Liquidtype = 13 (WMO Water)<br />
<br />
Else : Liquidtype = legacyLiquidType + 1<br />
<br />
<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_Slow_Water = 5,<br />
LIQUID_Slow_Ocean = 6,<br />
LIQUID_Slow_Magma = 7,<br />
LIQUID_Slow_Slime = 8,<br />
LIQUID_Fast_Water = 9,<br />
LIQUID_Fast_Ocean = 10,<br />
LIQUID_Fast_Magma = 11,<br />
LIQUID_Fast_Slime = 12,<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Water_Interior = 17,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
LIQUID_Coilfang_Raid_Water = 41,<br />
LIQUID_Hyjal_Past_Water = 61,<br />
LIQUID_Lake_Wintergrasp_Water = 81,<br />
LIQUID_Basic Procedural Water = 100,<br />
LIQUID_CoA_Black_Magma = 121,<br />
LIQUID_Chamber_Magma = 141,<br />
LIQUID_Orange_Slime = 181,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
// edit : the code above will turn any "green alva" to water, but green lava is used for both water and lava liquids in vanilla/bc models, so this is not right. Need to figure out how to determine if it is lava or water.<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
==MFVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.???}}<br />
uint16_t mapobject_fog_volume_refs[]; // into MFOG (and MFED)<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=Day_night_cycle&diff=34620Day night cycle2021-11-25T15:10:46Z<p>Zee: `</p>
<hr />
<div>== Summary ==<br />
<br />
IMPORTANT: Most of the information on this page was obtained by reversing a build of Mists of Pandaria (15662). It seems to hold true for Warlords of Draenor, as well, and may also apply to early versions of the Legion alpha and/or beta. It appears that later Legion builds changed shading logic a fair bit, at least as it relates to fog (adding a fog height plane and moving fog shading logic to the fragment shader, among other things).<br />
<br />
== CGameTime ==<br />
<br />
CGameTime contains the logic and values necessary to determine things like: time of day progression, adjustments necessary to transform local time in to server time, etc.<br />
<br />
=== CGameTime::GameTimeGetDayProgression ===<br />
<br />
The GameTimeGetDayProgression function in CGameTime is frequently used in the various DayNight calculations. This function calculates the minutes since midnight in server time, and divides by the total number of minutes in a day.<br />
<br />
The return value is a floating point that ranges from 0.0 to 1.0, measuring the distance the current server time is from midnight. 0.0 represents the time just after midnight, 0.5 represents midday, and 1.0 represents the time just before midnight.<br />
<br />
== DayNight ==<br />
<br />
=== DayNight Tables ===<br />
<br />
Several static tables are defined in the client's DayNight classes. These tables are used to modify colors and values involved in lighting. The tables are typically lerped by DayNight::InterpTable (see below).<br />
<br />
==== Known Tables ====<br />
<br />
The known DayNight tables as of MoP are:<br />
<br />
* DayNight::DNSky::s_darkTable<br />
** Determines a multiplier used to mute the m_highlightSky float present in LightParams.dbc<br />
* DayNight::DNSky::s_fadeTable<br />
* DayNight::DNStars::s_fadeTable<br />
* DayNight::DNClouds::s_bumpFadeTable<br />
<br />
==== Table Structure ====<br />
<br />
DayNight tables are structured like this:<br />
<br />
struct {<br />
float distance_along_interpolation;<br />
float value_at_distance;<br />
};<br />
<br />
For example, <br />
<br />
DayNight::DNStars::s_fadeTable = {<br />
{ 0.1250, 1.0 },<br />
{ 0.1875, 0.0 },<br />
{ 0.9375, 0.0 },<br />
{ 1.0000, 1.0 }<br />
};<br />
<br />
==== DayNight::s_sidnTable ====<br />
<br />
Determines the value stored in <tt>DayNight::CDayNightObject::GetSelfIlluminatedScalar</tt>.<br />
<br />
This value is used to control things like window glow at night (ie emissive lighting for <tt>SMOMaterial</tt>).<br />
<br />
<syntaxhighlight lang="cpp"><br />
DayNight::s_sidnTable = {<br />
{ 0.25, 1.0 },<br />
{ 0.291667, 0.0 },<br />
{ 0.854167, 0.0 },<br />
{ 0.895833, 1.0 }<br />
};<br />
</syntaxhighlight><br />
<br />
==== DayNight::CDayNightObjectInt::SetDirection(void)::phiTable ====<br />
<br />
Used to calculate sun direction (for use in shaders).<br />
<br />
<syntaxhighlight lang="cpp"><br />
DayNight::CDayNightObjectInt::SetDirection(void)::phiTable = {<br />
{ 0.0, 2.2165682315826416 }, // pi * 0.70555556<br />
{ 0.25, 1.919862151145935 }, // pi * 0.6111111<br />
{ 0.5, 2.2165682315826416 }, // pi * 0.70555556<br />
{ 0.75, 1.919862151145935 } // pi * 0.6111111<br />
};<br />
</syntaxhighlight><br />
<br />
==== DayNight::CDayNightObjectInt::SetDirection(void)::thetaTable ====<br />
<br />
Used to calculate sun direction (for use in shaders).<br />
<br />
<syntaxhighlight lang="cpp"><br />
DayNight::CDayNightObjectInt::SetDirection(void)::thetaTable = {<br />
{ 0.0, 3.9269907474517822 }, // pi * 1.25<br />
{ 0.25, 3.9269907474517822 }, // pi * 1.25<br />
{ 0.5, 3.9269907474517822 }, // pi * 1.25<br />
{ 0.75, 3.9269907474517822 } // pi * 1.25<br />
};<br />
</syntaxhighlight><br />
<br />
=== DayNight::InterpTable ===<br />
<br />
Given a table and an interpolation factor, this function returns a value linearly interpolated out of the table. The function is commonly called in various DayNight value / color update functions.<br />
<br />
Return values are always floating points.<br />
<br />
==== InterpTable Logic ====<br />
<br />
Logic WIP<br />
<br />
=== DayNight::DarkenColor ===<br />
<br />
Adjusts a given RGB value's brightness by a given floating point multiplier. Converts RGB to HSV, applies the multiplier to the V component, converts the adjusted HSV value back to RGB, and returns it.<br />
<br />
Called from the various SetColors functions within DayNight.<br />
<br />
==== DarkenColor Logic ====<br />
<br />
Logic WIP<br />
<br />
=== DayNight::CDayNightObjectInt::CalcFogRate ===<br />
<br />
Given a fog start distance and a fog end distance, this function uses a simple formula to calculate a fog rate. The formula makes use of a fog far clip, defined as: fogFarClip = (cameraFarClip > 700.0f) ? cameraFarClip - 200.f : 500.0f.<br />
<br />
Note that the fog start distance fed to this function is typically computed by this formula: fogStart = fogEnd * fogScalar.<br />
<br />
Also note that (at least from MoP+) calculating fog start from the various light DBC values uses a somewhat different formula than: fogStart = fogEnd * fogScalar. See CalcColors to get a sense of what changes happen to fogScalar before fogStart is calculated.<br />
<br />
==== CalcFogRate Logic ====<br />
<br />
<syntaxhighlight lang="cpp"><br />
float DayNight::CDayNightObjectInt::CalcFogRate(DayNight::CDayNightObjectInt *this, float fogStart, float fogEnd) {<br />
<br />
float fogRange = fogEnd - fogStart;<br />
<br />
float fogFarClip;<br />
<br />
if (this.info.cameraFarClip < 700.f) {<br />
fogFarClip = this.info.cameraFarClip - 200.0f;<br />
} else {<br />
fogFarClip = 500.f;<br />
}<br />
<br />
float fogRate;<br />
<br />
if (fogRange <= fogFarClip) {<br />
fogRate = ((1.0f - (fogRange / fogFarClip)) * 5.5f) + 1.5f;<br />
} else {<br />
fogRate = 1.5f;<br />
}<br />
<br />
return fogRate;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
=== DayNight::CDayNightObjectInt::CalcColors ===<br />
<br />
This function handles lerping / copying area light colors and parameters. Area lights are the lights defined by Light.dbc (and associated DBCs).<br />
<br />
==== CalcColors Logic ====<br />
<br />
<syntaxhighlight lang="cpp"><br />
int DayNight::CDayNightObjectInt::CalcColors(DayNight::CDayNightObjectInt *this, int time, DayNight::CurrentLight *currentLight, LightParamsRec *lightParamsRec) {<br />
<br />
// Not shown: obtain the appropriate DBC entries for the start and end<br />
float tstart = time - t1;<br />
float tdiff = t2 - t1;<br />
float tdist = tstart / tdiff;<br />
<br />
// Interpolate color values 0-17 from LightData.dbc<br />
currentLight->m_directColor = DayNight::LerpColor(tdist, start->m_directColor, end->m_directColor);<br />
currentLight->m_ambientColor = DayNight::LerpColor(tdist, start->m_ambientColor, end->m_ambientColor);<br />
currentLight->m_skyTopColor = DayNight::LerpColor(tdist, start->m_skyTopColor, end->m_skyTopColor);<br />
currentLight->m_skyMiddleColor = DayNight::LerpColor(tdist, start->m_skyMiddleColor, end->m_skyMiddleColor);<br />
currentLight->m_skyBand1Color = DayNight::LerpColor(tdist, start->m_skyBand1Color, end->m_skyBand1Color);<br />
currentLight->m_skyBand2Color = DayNight::LerpColor(tdist, start->m_skyBand2Color, end->m_skyBand2Color);<br />
currentLight->m_skySmogColor = DayNight::LerpColor(tdist, start->m_skySmogColor, end->m_skySmogColor);<br />
currentLight->m_skyFogColor = DayNight::LerpColor(tdist, start->m_skyFogColor, end->m_skyFogColor);<br />
currentLight->m_shadowOpacity = DayNight::LerpColor(tdist, start->m_shadowOpacity, end->m_shadowOpacity);<br />
currentLight->m_sunColor = DayNight::LerpColor(tdist, start->m_sunColor, end->m_sunColor);<br />
currentLight->m_cloudSunColor = DayNight::LerpColor(tdist, start->m_cloudSunColor, end->m_cloudSunColor);<br />
currentLight->m_cloudLayer1AmbientColor = DayNight::LerpColor(tdist, start->m_cloudLayer1AmbientColor, end->m_cloudLayer1AmbientColor);<br />
currentLight->m_cloudEmissiveColor = DayNight::LerpColor(tdist, start->m_cloudEmissiveColor, end->m_cloudEmissiveColor);<br />
currentLight->m_cloudLayer2AmbientColor = DayNight::LerpColor(tdist, start->m_cloudLayer2AmbientColor, end->m_cloudLayer2AmbientColor);<br />
currentLight->m_oceanCloseColor = DayNight::LerpColor(tdist, start->m_oceanCloseColor, end->m_oceanCloseColor);<br />
currentLight->m_oceanFarColor = DayNight::LerpColor(tdist, start->m_oceanFarColor, end->m_oceanFarColor);<br />
currentLight->m_riverCloseColor = DayNight::LerpColor(tdist, start->m_riverCloseColor, end->m_riverCloseColor);<br />
currentLight->m_riverFarColor = DayNight::LerpColor(tdist, start->m_riverFarColor, end->m_riverFarColor);<br />
<br />
// Handle cloud density<br />
currentLight->m_cloudDensity = start->m_cloudDensity + ((end->m_cloudDensity - start->m_cloudDensity) * tdist);<br />
<br />
// Copy light params (these values are not lerped)<br />
currentLight->m_highlightSky = lightParamsRec->m_highlightSky;<br />
currentLight->m_glow = lightParamsRec->m_glow;<br />
currentLight->m_waterShallowAlpha = lightParamsRec->m_waterShallowAlpha;<br />
currentLight->m_waterDeepAlpha = lightParamsRec->m_waterDeepAlpha;<br />
currentLight->m_oceanShallowAlpha = lightParamsRec->m_oceanShallowAlpha;<br />
currentLight->m_oceanDeepAlpha = lightParamsRec->m_oceanDeepAlpha;<br />
<br />
// Light params flag 0x01 set to off (unk?)<br />
if (!(lightParamsRec->m_flags & 0x01))<br />
currentLight->float7C = 0.0;<br />
} else {<br />
currentLight->float7C = 1.0;<br />
}<br />
<br />
// Light params flag 0x02 (?) set to on (unk?)<br />
currentLight->dword80 = (lightParamsRec->m_flags >> 1) & 0x01;<br />
<br />
currentLight->dword90 = 1.0;<br />
currentLight->dwordA8 = 1.0;<br />
currentLight->m_lightSkyboxID = lightParamsRec->m_lightSkyboxID;<br />
currentLight->m_cloudTypeID = lightParamsRec->m_cloudTypeID;<br />
<br />
// Handle fog<br />
currentLight->m_fogEnd = (start->m_fogEnd + ((end->m_fogEnd - start->m_fogEnd) * tdist)) / 36.0;<br />
currentLight->m_fogEnd = currentLight->m_fogEnd < 10.0f ? 10.0f : currentLight->m_fogEnd;<br />
<br />
currentLight->m_fogScalar = start->m_fogScalar + ((end->m_fogScalar - start->m_fogScalar) * tdist);<br />
currentLight->m_fogScalar = clamp(currentLight->m_fogScalar, -1.0, 1.0);<br />
<br />
currentLight->m_fogRate = 1.0f;<br />
<br />
// Fog not enabled? (unk)<br />
if (this->m_info.unk_0 != 1) {<br />
return 1;<br />
}<br />
<br />
if (currentLight->m_fogEnd >= 27.777779f) {<br />
float fogRange = currentLight->m_fogEnd - (currentLight->m_fogScalar * currentLight->m_fogEnd);<br />
<br />
float fogFarClip;<br />
<br />
if (this->m_info.m_cameraFarClip < 700.0f) {<br />
fogFarClip = this->m_info.m_cameraFarClip - 200.0f;<br />
} else {<br />
fogFarClip = 500.0;<br />
}<br />
<br />
float fogRate;<br />
<br />
if (fogRange <= fogFarClip) {<br />
fogRate = ((1.0f - (fogRange / fogFarClip)) * 5.5f) + 1.5f;<br />
} else {<br />
fogRate = 1.5f;<br />
}<br />
<br />
currentLight->m_fogRate = fogRate;<br />
currentLight->m_fogEnd = this->m_info.m_cameraFarClip;<br />
}<br />
<br />
currentLight->m_fogScalar = max(0.0f, currentLight->m_fogScalar);<br />
<br />
return 1;<br />
}<br />
</syntaxhighlight><br />
<br />
== CShaderEffect ==<br />
<br />
=== CShaderEffect::SetFogParams ===<br />
<br />
The given fog params and fog color set up to be passed to the GPU when this function is called. Values typically or always originate from DayNight.<br />
<br />
==== SetFogParams Logic ====<br />
<br />
<syntaxhighlight lang="cpp"><br />
void CShaderEffect::SetFogParams(float fogStart, float fogEnd, float fogRate, const CArgb *fogColor, float a5) {<br />
if (CShaderEffect::s_enableShaders) {<br />
CShaderEffect::s_fogColorAlphaRef = fogColor / 255.0f;<br />
<br />
float fogRange = fogEnd - fogStart;<br />
<br />
// s_fogMul is set to 1.0 in CShaderEffect::InitShaderSystem, and does not appear to be modified elsewhere<br />
float fogMul = CShaderEffect::s_fogMul;<br />
<br />
CShaderEffect::s_fogParams.x = -(1.0f / fogRange) * fogMul;<br />
CShaderEffect::s_fogParams.y = (1.0f / fogRange) * fogEnd;<br />
CShaderEffect::s_fogParams.z = fogRate;<br />
<br />
// w is unknown; often forcibly set to 0.0; possibly something to do with intersecting m_liquidPlane in CM2SceneRender::SetupLighting<br />
CShaderEffect::s_fogParams.w = a5;<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== Rendering ==<br />
<br />
The data for the colors used to render the skybox come from LightData.db2 and are interpolated using the functions described above and current game time.<br />
<br />
Choosing the correct row in the LightData.db2 is done by checking in which of the polygons generated from ZoneLight.db2/ZoneLightPoint.db2 the camera resides in.<br />
<br />
The game also interpolates between 2 LightData.db2 entries when navigating between the zone light polygons.<br />
<br />
=== Sky Cone ===<br />
<br />
==== Method 1 ====<br />
The blizzard way of rendering the sky cone is to generate a mesh at runtime that looks like this https://i.imgur.com/jkDxHqD.png (thanks Deamon), setting the vertex colors based on the values from LightData.db2, and let the <br />
blending of the colors be done automatically by the shader.<br />
<br />
==== Method 2 ====<br />
It can also be achieved procedurally by rendering a regular sphere/cube and using lerp(mix), smoothstep to manually interpolate between the input colors, using elevation as the blend factor.<br />
<br />
Elevation can be calculated like so<br />
float3 fragDir = normalize(worldPos - worldCameraPos);<br />
float3 upDir = float3(0, 1, 0)<br />
float3 elevation = dot(fragDir, upDir); // -1 .. 1<br />
elevation = (1 + elevation) / 2; // 0..1<br />
<br />
Here's a vertical panoramic showing where each value in the LightData.db2 is used: https://i.imgur.com/3ArucTD.png<br />
<br />
And a table of guessed interpolation stops (I literally just eyeballed till they looked like in game)<br />
SkyTop - 1 <> 0.714<br />
SkyMiddle - 0.714 <> 0.547<br />
SkyBand1 - 0.547 <> 0.513<br />
SkyBand2 - 0.513 <> 0.5<br />
SkySmog - 0.5 <> 0.486<br />
SkyFog - 0.486 <> 0</div>Zeehttps://wowdev.wiki/index.php?title=Rendering/DayNight&diff=34344Rendering/DayNight2021-08-21T13:31:56Z<p>Zee: </p>
<hr />
<div>= Overview =<br />
<br />
In World of Warcraft, the <tt>DayNight</tt> system governs most of the general appearance of the world.<br />
<br />
Things like area lighting, zone lighting, clouds, the sky sphere (ie the color gradient of the sky), stars (ie sky boxes) are all managed by <tt>DayNight</tt>.<br />
<br />
= Utility Functions =<br />
<br />
== InterpTable ==<br />
Interpolate between table values of the tables used below.<br />
<br />
count - the number of rows in the table (Vec2's), usually 4<br />
<br />
dayProgression - internal time normalized (time / 2880f)<br />
<br />
<syntaxhighlight lang="cpp"><br />
double InterpTable(C2Vector[] table, uint count, float dayProgression)<br />
{<br />
unsigned int idxA = 0;<br />
int idxB;<br />
<br />
if (count != 0)<br />
{<br />
do<br />
{<br />
if (dayProgression <= (double)(table[idxA].x))<br />
break;<br />
++idxA;<br />
}<br />
while (idxA < count);<br />
}<br />
if (idxA == count)<br />
{<br />
idxA = 0;<br />
idxB = (int)(count - 1);<br />
}<br />
else if (idxA != 0)<br />
{<br />
idxB = (int)(idxA - 1);<br />
}<br />
else<br />
{<br />
idxB = (int)(count - 1);<br />
}<br />
double v6 = table[idxA].x - table[idxB].x;<br />
if (v6 < 0.0)<br />
v6 = v6 + 1.0;<br />
double v7 = dayProgression - table[idxB].x;<br />
if (v7 < 0.0)<br />
v7 = v7 + 1.0;<br />
double v8 = v7 / v6;<br />
if (table[idxA].y < table[idxB].y)<br />
return table[idxB].y - v8 * (table[idxB].y - table[idxA].y);<br />
else<br />
return table[idxB].y + v8 * (table[idxA].y - table[idxB].y);<br />
}<br />
<br />
</syntaxhighlight><br />
<br />
== DayNight::DarkenColor ==<br />
<br />
<syntaxhighlight lang="cpp"><br />
CImVector DayNight::DarkenColor(CImVector const &clr, float amount) {<br />
<br />
C3Vector rgb;<br />
C3Vector hsv;<br />
<br />
rgb.x = clr->r / 255.0;<br />
rgb.y = clr->g / 255.0;<br />
rgb.z = clr->b / 255.0;<br />
<br />
hsv.x = 0.0;<br />
hsv.y = 0.0;<br />
hsv.z = 0.0;<br />
<br />
RGBtoHSV(&rgb, &hsv);<br />
<br />
hsv.z = hsv.z * amount;<br />
<br />
HSVtoRGB(&hsv, &rgb);<br />
<br />
CImVector result;<br />
<br />
CImVector::operator=(&result, &rgb);<br />
<br />
return result;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
= Lighting Functions =<br />
<br />
== DayNight::SetDirection ==<br />
<br />
<tt>DayNight::SetDirection</tt> calculates a <tt>C3Vector</tt> containing the direction vector for the global light, aka <tt>sunDir</tt>.<br />
<br />
This direction vector is transformed by the view matrix prior to being uploaded to the shader.<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::SetDirection() {<br />
<br />
// Phi Table<br />
if ( !(dword_D39104 & 1) ) {<br />
<br />
dword_D39104 |= 1u;<br />
<br />
DayNight::phiTable = {<br />
<br />
{ 0.0, 2.2165682 },<br />
{ 0.25, 1.9198623 },<br />
{ 0.5, 2.2165682 },<br />
{ 0.75, 1.9198623 }<br />
<br />
};<br />
<br />
}<br />
<br />
// Theta Table<br />
if ( !(dword_D39104 & 2) ) {<br />
<br />
dword_D39104 |= 2;<br />
<br />
DayNight::thetaTable = {<br />
<br />
{ 0.0, 3.926991 },<br />
{ 0.25, 3.926991 },<br />
{ 0.5, 3.926991 },<br />
{ 0.75, 3.926991 }<br />
<br />
};<br />
<br />
}<br />
<br />
float phi = DayNight::InterpTable(&DayNight::phiTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
float theta = DayNight::InterpTable(&DayNight::thetaTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
<br />
// Convert from spherical coordinates to XYZ<br />
// x = rho * sin(phi) * cos(theta)<br />
// y = rho * sin(phi) * sin(theta)<br />
// z = rho * cos(phi)<br />
<br />
float sinPhi = CMath::sinoid(phi);<br />
float cosPhi = CMath::cosoid(phi);<br />
<br />
float sinTheta = CMath::sinoid(theta);<br />
float cosTheta = CMath::cosoid(theta);<br />
<br />
DayNight::g_dnInfo.lightInfo.dir.x = sinPhi * cosTheta;<br />
DayNight::g_dnInfo.lightInfo.dir.y = sinPhi * sinTheta;<br />
DayNight::g_dnInfo.lightInfo.dir.z = cosPhi;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
== DayNight::SetLightColors ==<br />
<br />
Sets up lighting colors in <tt>DNInfo->lightInfo</tt>. The colors in <tt>DNInfo->lightInfo</tt> directly feed the lighting for ADTs, M2s, WMOs, etc.<br />
<br />
For WMO materials with the <tt>F_WINDOW</tt> flag set, the <tt>windowAmbColor</tt> and <<tt>windowDirColor</tt> are used.<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::SetLightColors() {<br />
<br />
// Light colors<br />
<br />
DayNight::g_dnInfo.lightInfo.dirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
DayNight::g_dnInfo.lightInfo.ambColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
// Window light colors<br />
// MOMT.flags & F_WINDOW<br />
<br />
CImVector windowDirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
CImVector::Blend255_(&windowDirColor, 128u, &DayNight::g_dnInfo.light.ambColor);<br />
<br />
CImVector windowAmbColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
CImVector::Blend255_(&windowAmbColor, 128u, &DayNight::g_dnInfo.light.dirColor);<br />
<br />
windowAmbColor.r = std::min(windowAmbColor.r + 16u, 255u);<br />
windowAmbColor.g = std::min(windowAmbColor.g + 16u, 255u);<br />
windowAmbColor.b = std::min(windowAmbColor.b + 16u, 255u);<br />
windowAmbColor.a = windowAmbColor.a - 1; // 0 wraps to 255<br />
<br />
DayNight::g_dnInfo.lightInfo.windowDirColor = windowDirColor;<br />
<br />
DayNight::g_dnInfo.lightInfo.windowAmbColor = windowAmbColor;<br />
<br />
// Shadow color<br />
// TODO: Clean up<br />
<br />
int v1;<br />
<br />
BYTE1(v1) = (unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.r + 3)) >> 8;<br />
LOBYTE(v1) = (unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.g + 3)) >> 8;<br />
DayNight::g_dnInfo.shadowColor = (CImVector)(*(_DWORD *)&DayNight::g_dnInfo.light.ambColor & 0xFF000000 | ((unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.b + 3)) >> 8) | ((unsigned __int16)v1 << 8));<br />
DayNight::g_dnInfo.shadowColor.a = DayNight::g_dnInfo.light.shadowOpacity.r;<br />
<br />
// TODO: What is unkLightInfo?<br />
<br />
DayNight::g_dnInfo.unkLightInfo.dir.x = DayNight::g_dnInfo.lightInfo.dir.x;<br />
DayNight::g_dnInfo.unkLightInfo.dir.y = DayNight::g_dnInfo.lightInfo.dir.y;<br />
DayNight::g_dnInfo.unkLightInfo.dir.z = DayNight::g_dnInfo.lightInfo.dir.z;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.dirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.ambColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.windowDirColor = DayNight::g_dnInfo.lightInfo.windowDirColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.windowAmbColor = DayNight::g_dnInfo.lightInfo.windowAmbColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.x = DayNight::g_dnInfo.lightInfo.shaderShadowColor.x;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.y = DayNight::g_dnInfo.lightInfo.shaderShadowColor.y;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.z = DayNight::g_dnInfo.lightInfo.shaderShadowColor.z;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.w = DayNight::g_dnInfo.lightInfo.shaderShadowColor.w;<br />
<br />
// Shader shadow color<br />
<br />
C3Vector rgb;<br />
<br />
rgb.x = DayNight::g_dnInfo.light.ambColor.r / 255.0f;<br />
rgb.y = DayNight::g_dnInfo.light.ambColor.g / 255.0f;<br />
rgb.z = DayNight::g_dnInfo.light.ambColor.b / 255.0f;<br />
<br />
C3Vector hsv;<br />
<br />
hsv.x = 0.0;<br />
hsv.y = 0.0;<br />
hsv.z = 0.0;<br />
<br />
RGBtoHSV(&rgb, &hsv);<br />
<br />
hsv.y = hsv.y * 0.33000001;<br />
hsv.z = hsv.z * 1.25;<br />
<br />
HSVtoRGB(&hsv, &rgb);<br />
<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.x = rgb.x;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.y = rgb.y;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.z = rgb.z;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.w = 1.0;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
= Stars =<br />
<br />
<tt>DNStars</tt> controls the skybox (the model that overlays the sky sphere).<br />
<br />
== DayNight::DNStars::Update ==<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::DNStars::Update(DNStars *this) {<br />
<br />
<br />
DayNight::DNStars::s_fadeTable = {<br />
<br />
{ 0.1250f, 1f },<br />
{ 0.1875f, 0f },<br />
{ 0.9374f, 0f },<br />
{ 0.9999f, 1f }<br />
<br />
};<br />
<br />
<br />
this->pos.x = DayNight::g_dnInfo.cameraPos.x;<br />
this->pos.y = DayNight::g_dnInfo.cameraPos.y;<br />
this->pos.z = DayNight::g_dnInfo.cameraPos.z;<br />
<br />
float fade = DayNight::InterpTable(&DayNight::DNStars::s_fadeTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
<br />
this->color.a = (signed int)(fade * 254.0 + 1.0);<br />
<br />
}<br />
</syntaxhighlight></div>Zeehttps://wowdev.wiki/index.php?title=Rendering/DayNight&diff=34343Rendering/DayNight2021-08-21T13:12:03Z<p>Zee: </p>
<hr />
<div>= Overview =<br />
<br />
In World of Warcraft, the <tt>DayNight</tt> system governs most of the general appearance of the world.<br />
<br />
Things like area lighting, zone lighting, clouds, the sky sphere (ie the color gradient of the sky), stars (ie sky boxes) are all managed by <tt>DayNight</tt>.<br />
<br />
= Utility Functions =<br />
<br />
== DayNight::DarkenColor ==<br />
<br />
<syntaxhighlight lang="cpp"><br />
CImVector DayNight::DarkenColor(CImVector const &clr, float amount) {<br />
<br />
C3Vector rgb;<br />
C3Vector hsv;<br />
<br />
rgb.x = clr->r / 255.0;<br />
rgb.y = clr->g / 255.0;<br />
rgb.z = clr->b / 255.0;<br />
<br />
hsv.x = 0.0;<br />
hsv.y = 0.0;<br />
hsv.z = 0.0;<br />
<br />
RGBtoHSV(&rgb, &hsv);<br />
<br />
hsv.z = hsv.z * amount;<br />
<br />
HSVtoRGB(&hsv, &rgb);<br />
<br />
CImVector result;<br />
<br />
CImVector::operator=(&result, &rgb);<br />
<br />
return result;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
= Lighting Functions =<br />
<br />
== DayNight::SetDirection ==<br />
<br />
<tt>DayNight::SetDirection</tt> calculates a <tt>C3Vector</tt> containing the direction vector for the global light, aka <tt>sunDir</tt>.<br />
<br />
This direction vector is transformed by the view matrix prior to being uploaded to the shader.<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::SetDirection() {<br />
<br />
// Phi Table<br />
if ( !(dword_D39104 & 1) ) {<br />
<br />
dword_D39104 |= 1u;<br />
<br />
DayNight::phiTable = {<br />
<br />
{ 0.0, 2.2165682 },<br />
{ 0.25, 1.9198623 },<br />
{ 0.5, 2.2165682 },<br />
{ 0.75, 1.9198623 }<br />
<br />
};<br />
<br />
}<br />
<br />
// Theta Table<br />
if ( !(dword_D39104 & 2) ) {<br />
<br />
dword_D39104 |= 2;<br />
<br />
DayNight::thetaTable = {<br />
<br />
{ 0.0, 3.926991 },<br />
{ 0.25, 3.926991 },<br />
{ 0.5, 3.926991 },<br />
{ 0.75, 3.926991 }<br />
<br />
};<br />
<br />
}<br />
<br />
float phi = DayNight::InterpTable(&DayNight::phiTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
float theta = DayNight::InterpTable(&DayNight::thetaTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
<br />
// Convert from spherical coordinates to XYZ<br />
// x = rho * sin(phi) * cos(theta)<br />
// y = rho * sin(phi) * sin(theta)<br />
// z = rho * cos(phi)<br />
<br />
float sinPhi = CMath::sinoid(phi);<br />
float cosPhi = CMath::cosoid(phi);<br />
<br />
float sinTheta = CMath::sinoid(theta);<br />
float cosTheta = CMath::cosoid(theta);<br />
<br />
DayNight::g_dnInfo.lightInfo.dir.x = sinPhi * cosTheta;<br />
DayNight::g_dnInfo.lightInfo.dir.y = sinPhi * sinTheta;<br />
DayNight::g_dnInfo.lightInfo.dir.z = cosPhi;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
== DayNight::SetLightColors ==<br />
<br />
Sets up lighting colors in <tt>DNInfo->lightInfo</tt>. The colors in <tt>DNInfo->lightInfo</tt> directly feed the lighting for ADTs, M2s, WMOs, etc.<br />
<br />
For WMO materials with the <tt>F_WINDOW</tt> flag set, the <tt>windowAmbColor</tt> and <<tt>windowDirColor</tt> are used.<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::SetLightColors() {<br />
<br />
// Light colors<br />
<br />
DayNight::g_dnInfo.lightInfo.dirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
DayNight::g_dnInfo.lightInfo.ambColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
// Window light colors<br />
// MOMT.flags & F_WINDOW<br />
<br />
CImVector windowDirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
CImVector::Blend255_(&windowDirColor, 128u, &DayNight::g_dnInfo.light.ambColor);<br />
<br />
CImVector windowAmbColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
CImVector::Blend255_(&windowAmbColor, 128u, &DayNight::g_dnInfo.light.dirColor);<br />
<br />
windowAmbColor.r = std::min(windowAmbColor.r + 16u, 255u);<br />
windowAmbColor.g = std::min(windowAmbColor.g + 16u, 255u);<br />
windowAmbColor.b = std::min(windowAmbColor.b + 16u, 255u);<br />
windowAmbColor.a = windowAmbColor.a - 1; // 0 wraps to 255<br />
<br />
DayNight::g_dnInfo.lightInfo.windowDirColor = windowDirColor;<br />
<br />
DayNight::g_dnInfo.lightInfo.windowAmbColor = windowAmbColor;<br />
<br />
// Shadow color<br />
// TODO: Clean up<br />
<br />
int v1;<br />
<br />
BYTE1(v1) = (unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.r + 3)) >> 8;<br />
LOBYTE(v1) = (unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.g + 3)) >> 8;<br />
DayNight::g_dnInfo.shadowColor = (CImVector)(*(_DWORD *)&DayNight::g_dnInfo.light.ambColor & 0xFF000000 | ((unsigned __int16)(85 * (DayNight::g_dnInfo.light.ambColor.b + 3)) >> 8) | ((unsigned __int16)v1 << 8));<br />
DayNight::g_dnInfo.shadowColor.a = DayNight::g_dnInfo.light.shadowOpacity.r;<br />
<br />
// TODO: What is unkLightInfo?<br />
<br />
DayNight::g_dnInfo.unkLightInfo.dir.x = DayNight::g_dnInfo.lightInfo.dir.x;<br />
DayNight::g_dnInfo.unkLightInfo.dir.y = DayNight::g_dnInfo.lightInfo.dir.y;<br />
DayNight::g_dnInfo.unkLightInfo.dir.z = DayNight::g_dnInfo.lightInfo.dir.z;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.dirColor = DayNight::g_dnInfo.light.dirColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.ambColor = DayNight::g_dnInfo.light.ambColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.windowDirColor = DayNight::g_dnInfo.lightInfo.windowDirColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.windowAmbColor = DayNight::g_dnInfo.lightInfo.windowAmbColor;<br />
<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.x = DayNight::g_dnInfo.lightInfo.shaderShadowColor.x;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.y = DayNight::g_dnInfo.lightInfo.shaderShadowColor.y;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.z = DayNight::g_dnInfo.lightInfo.shaderShadowColor.z;<br />
DayNight::g_dnInfo.unkLightInfo.shaderShadowColor.w = DayNight::g_dnInfo.lightInfo.shaderShadowColor.w;<br />
<br />
// Shader shadow color<br />
<br />
C3Vector rgb;<br />
<br />
rgb.x = DayNight::g_dnInfo.light.ambColor.r / 255.0f;<br />
rgb.y = DayNight::g_dnInfo.light.ambColor.g / 255.0f;<br />
rgb.z = DayNight::g_dnInfo.light.ambColor.b / 255.0f;<br />
<br />
C3Vector hsv;<br />
<br />
hsv.x = 0.0;<br />
hsv.y = 0.0;<br />
hsv.z = 0.0;<br />
<br />
RGBtoHSV(&rgb, &hsv);<br />
<br />
hsv.y = hsv.y * 0.33000001;<br />
hsv.z = hsv.z * 1.25;<br />
<br />
HSVtoRGB(&hsv, &rgb);<br />
<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.x = rgb.x;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.y = rgb.y;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.z = rgb.z;<br />
DayNight::g_dnInfo.lightInfo.shaderShadowColor.w = 1.0;<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
= Stars =<br />
<br />
<tt>DNStars</tt> controls the skybox (the model that overlays the sky sphere).<br />
<br />
== DayNight::DNStars::Update ==<br />
<br />
<syntaxhighlight lang="cpp"><br />
void DayNight::DNStars::Update(DNStars *this) {<br />
<br />
<br />
DayNight::DNStars::s_fadeTable = {<br />
<br />
{ 0.1250f, 1f },<br />
{ 0.1875f, 0f },<br />
{ 0.9374f, 0f },<br />
{ 0.9999f, 1f }<br />
<br />
};<br />
<br />
<br />
this->pos.x = DayNight::g_dnInfo.cameraPos.x;<br />
this->pos.y = DayNight::g_dnInfo.cameraPos.y;<br />
this->pos.z = DayNight::g_dnInfo.cameraPos.z;<br />
<br />
float fade = DayNight::InterpTable(&DayNight::DNStars::s_fadeTable, 4u, DayNight::g_dnInfo.dayProgression);<br />
<br />
this->color.a = (signed int)(fade * 254.0 + 1.0);<br />
<br />
}<br />
</syntaxhighlight></div>Zeehttps://wowdev.wiki/index.php?title=ADT/v18&diff=34341ADT/v182021-08-18T00:47:18Z<p>Zee: </p>
<hr />
<div>[[ADT]] files contain terrain and object information for map tiles. They have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
A map tile is split up into 16x16 = 256 map chunks. (not the same as file chunks, although each map chunk will have its own file chunk :) ) So there will be a few initial data chunks to specify textures, objects, models, etc. followed by 256 [[ADT#MCNK_chunk|MCNK]] (mapchunk) chunks :) Each [[ADT#MCNK_chunk|MCNK]] chunk has a small header of its own, and additional chunks within its data block, following the same id-size-data format.<br />
<br />
== Terminology Reference ==<br />
{| class="wikitable"<br />
|-<br />
! Term !! Explanation<br />
|-<br />
| MapChunk (Chunk) || Refers to a chunk (terrain cell) represented by [[ADT#MCNK_chunk|MCNK]] data chunk.<br />
|-<br />
| MapTile (Tile) || Referes to one .ADT file (<{{Template:Sandbox/PrettyVersion|expansionlevel=4}}) or a group of ADT-related files representing one MapTile in [[WDT]].<br />
|-<br />
| Sub-chunk (used in some software) || Commonly used term to describe an area in [[ADT#MCNK_chunk|MCNK]]'s heightmap. See MCNK holes for understanding. It is an abstraction, and is not represented by any data structure.<br />
|}<br />
<br />
== An important note about the coordinate system used ==<br />
Wow's main coordinate system is [https://en.wikipedia.org/wiki/Right-hand_rule#Coordinates right-handed]; understanding it is very important in order to correctly interpret the ADT files.<br />
<br />
It's important to remember that:<br />
* The positive X-axis points north, the positive Y-axis points west. <br />
* The Z-axis is vertical height, with 0 being sea level. <br />
* The origin of the coordinate system is in the center of the map.<br />
* The top-left corner of the map has X = 17066, Y = 17066<br />
* The bottom-right corner of the map has X = -17066, Y = -17066<br />
* The bottom-left corner of the map has X = -17006, Y = 17066<br />
* The top-right corner of the map has X = 17006, Y = -17066<br />
<br />
Just to be absolutely clear, assuming you playing a character that is not flying or swimming and is facing north:<br />
* Forward = Vector3(1, 0, 0)<br />
* Right = Vector3(0, -1, 0)<br />
* Up = Vector3(0, 0, 1);<br />
<br />
This is the coordinate system used internally in all of the network packets and on most chunks in ADT files. Here is an overview of the other used [[#Coordinate_System_Translation|coordinate systems]].<br />
<br />
== Map size, blocks, chunks ==<br />
=== Introduction ===<br />
All maps are divided into 64x64 '''blocks''' for a total of 4096 (some of which may be unused). Each block are divided into 16x16 '''chunks''' (not to be confused with for example the file chunks, such as the "MHDR" chunk.. Completely different thing!). While like I said blocks can be unused, each block will always use all of its 16x16 chunks. <br />
<br />
=== Map size ===<br />
Each block is 533.33333 yards (1600 feet) in width and height. The map is divided into 64x64 blocks so the total width and height of the map will be 34133.33312 yards, however the origin of the coordinate system is at the center of the map so the minimum and maximum X and Y coordinates will be ±17066.66656).<br />
<br />
Since each block has 16x16 chunks, the size of a chunk will be 33.3333 yards (100 feet).<br />
<br />
=== Player's speed ===<br />
Basic running speed of a player (without any speed modifying effects) is 7.1111 yards/s (21.3333 feet/s). Player is able to reach one border of an ADT tile from another in 75 seconds. Thus, the fastest mounts (310%) can get over ADT size in 24.2 seconds.<br />
<br />
=== ADT files and blocks ===<br />
There is an .adt file for each existing block. If a block is unused it won't have an .adt file. The file will be: '''World/Maps/<InternalMapName>/<InternalMapName>_<BlockX>_<BlockY>.adt'''.<br />
<br />
* '''<InternalMapName>''' - {{Template:DBField|table=Map|column=m_Directory}}<br />
* '''<BlockX>''' - Index of the tile on the X axis<br />
* '''<BlockY>''' - Index of the tile on the Y axis<br />
<br />
Converting ADT co-ords to block X/Y can be done with the following formula (where axis is x or y): floor((32 - (axis / 533.33333)))<br />
<br />
=== Height ===<br />
<br />
The previous section details on X and Y limits only. The Z (height) limit is only implicit by stuff breaking slowly, like [[#MFBO_chunk_.28BC.2B.29|MFBO]] which is limited by using signed shorts, i.e. 2^15 being their max height. If you don't use [[#MFBO_chunk_.28BC.2B.29|MFBO]], there are some database files like [[DB/ZoneLight]] or world map related ones that also take height into account and may not be using floats depending on game version. [[DB/ZoneLight]] appears to be using -64000 and 64000 as the default if designers didn't put anything. [[DB/DungeonMapChunk]] seems to use -10000 for lower default. [[DB/UIMapAssignment]] wins with defaulting to -1000000 and 1000000.<br />
<br />
==split files (Cata+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
Beginning with Cataclysm, ADTs are split into multiple files: .adt (root), _tex%d.adt (tex) and _obj%d.adt (obj) with %d being the level of detail (0 or 1). Chunks are distributed over the files. To load a map, the client loads a set of three and treats them as one. While the [[Media:Mjo_adt.png|distribution schema]] appears to be quite fixed, the client does not keep the semantics of which file is which and parses them all the same.<br />
<br />
The main difference content-wise is [[ADT#MCIN_chunk_.28.3CCata.29|MCIN]] being gone, and [[ADT#MCNK_chunk|MCNK]] in tex and obj files not having the header it has in root files.<br />
<br />
{{Template:Sandbox/PrettyVersion|expansionlevel=7}} added _lod.adt (lod) files as another type. They are used for increased draw distance, this time including low quality versions of liquids and geometry as well (in the end, root lod bands).<br />
<br />
I've written a short guide on how to implement _lod.adt [[ADTLodImplementation]] [[User:Zee|Zee]]<br />
== MVER chunk ==<br />
* split files: all<br />
struct MVER {<br />
uint32_t version;<br />
};<br />
<br />
== MHDR chunk ==<br />
* split files: root<br />
*'''Contains offsets relative to &MHDR.data in the file for specific chunks.''' WoW only takes this for parsing the ADT file.<br />
struct SMMapHeader {<br />
enum MHDRFlags {<br />
mhdr_MFBO = 1, ''// contains a MFBO chunk.''<br />
mhdr_northrend = 2, ''// is set for some northrend ones.''<br />
};<br />
uint32_t flags;<br />
[[ADT#MCIN_chunk|MCIN]]* mcin; // Cata+: obviously gone. probably all offsets gone, except mh2o(which remains in root file).<br />
[[ADT#MTEX_chunk|MTEX]]* mtex;<br />
[[ADT#MMDX_chunk|MMDX]]* mmdx;<br />
[[ADT#MMID_chunk|MMID]]* mmid;<br />
[[ADT#MWMO_chunk|MWMO]]* mwmo;<br />
[[ADT#MWID_chunk|MWID]]* mwid;<br />
[[ADT#MDDF_chunk|MDDF]]* mddf;<br />
[[ADT#MODF_chunk|MODF]]* modf;<br />
[[ADT#MFBO_chunk|MFBO]]* mfbo; ''// this is only set if flags & mhdr_MFBO.''<br />
[[ADT#MH2O_chunk|MH2O]]* mh2o;<br />
[[ADT#MTXF_chunk_.28WotLK.2B.29|MTXF]]* mtxf;<br />
uint8_t mamp_value; // Cata+, explicit MAMP chunk overrides data<br />
uint8_t padding[3];<br />
uint32_t unused[3];<br />
} mhdr;<br />
<br />
== MCIN chunk (<Cata)==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=3|note=No longer possible due to [[#split_files_.28Cata.2B.29|split files]]}}<br />
*'''Pointers to [[ADT#MCNK_chunk|MCNK]] chunks and their sizes.'''<br />
struct SMChunkInfo<br />
{<br />
uint32_t offset; ''// absolute offset.''<br />
uint32_t size; ''// the size of the MCNK chunk, this is refering to.''<br />
uint32_t flags; ''// always 0. only set in the client.'', FLAG_LOADED = 1<br />
union<br />
{<br />
char pad[4]; <br />
uint32_t asyncId; ''// not in the adt file. client use only ''<br />
};<br />
} mcin[16*16];<br />
<br />
== MTEX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28294|max_expansionlevel=8|max_exclusive=1|note=MTEX has been replaced with file data ids in [[ADT/v18#MDID|MDID]] and [[ADT/v18#MHID|MHID]] chunks}}<br />
* split files: tex<br />
*'''List of textures used for texturing the terrain in this map tile.'''<br />
struct MTEX {<br />
char filenames[0]; ''// zero-terminated strings with complete paths to textures. Referenced in [[ADT/v18#MCLY_sub-chunk|MCLY]].''<br />
};<br />
<br />
==MDID==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}}<br />
{{Template:SectionBox|split files: tex0}}<br />
struct {<br />
/*0x00*/ uint32_t file_data_id; // _s.blp<br />
} diffuse_texture_ids[];<br />
<br />
==MHID==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}}<br />
{{Template:SectionBox|split files: tex0}}<br />
struct {<br />
/*0x00*/ uint32_t file_data_id; // _h.blp; 0 if there is none<br />
} height_texture_ids[diffuse_texture_ids.size];<br />
<br />
== MMDX chunk ==<br />
* split files: obj<br />
*'''List of filenames for [[M2]] models that appear in this map tile.'''<br />
struct MMDX {<br />
char filenames[0]; ''// zero-terminated strings with complete paths to models. Referenced in [[ADT/v18#MMID_chunk|MMID]].''<br />
};<br />
<br />
== MMID chunk ==<br />
* split files: obj<br />
*'''List of offsets of model filenames in the [[ADT#MMDX_chunk|MMDX]] chunk.'''<br />
struct MMID {<br />
uint32_t offsets[0]; ''// filename starting position in [[ADT/v18#MMDX_chunk|MMDX]] chunk. These entries are getting referenced in the [[ADT/v18#MDDF_chunk|MDDF]] chunk.''<br />
};<br />
<br />
== MWMO chunk ==<br />
* split files: obj<br />
*'''List of filenames for [[WMO]]s (world map objects) that appear in this map tile.'''<br />
struct MWMO {<br />
char filenames[0]; ''// zero-terminated strings with complete paths to models. Referenced in [[ADT/v18#MMID_chunk|MWID]].''<br />
};<br />
<br />
== MWID chunk ==<br />
* split files: obj<br />
*'''List of offsets of WMO filenames in the [[ADT#MWMO_chunk|MWMO]] chunk.'''<br />
struct MWID {<br />
uint32_t offsets[0]; ''// filename starting position in [[ADT/v18#MWMO_chunk|MWMO]] chunk. These entries are getting referenced in the [[ADT/v18#MODF_chunk|MODF]] chunk.''<br />
};<br />
<br />
== MDDF chunk ==<br />
* split files: obj<br />
*'''Placement information for doodads ([[M2]] models).''' Additional to this, the models to render are referenced in each [[ADT/v18#MCRF_sub-chunk|MCRF]] chunk.<br />
enum MDDFFlags {<br />
mddf_biodome = 1, ''// this sets internal flags to | 0x800 (WDOODADDEF.var0xC).''<br />
mddf_shrubbery = 2, ''// the actual meaning of these is unknown to me. maybe biodome is for really big M2s. 6.0.1.18179 seems ''<br />
''// not to check for this flag''<br />
mddf_unk_4 = 0x4, ''// {{Template:Unverified|Legion+}}''<br />
mddf_unk_8 = 0x8, ''// {{Template:Unverified|Legion+}}''<br />
SMDoodadDef::Flag_liquidKnown = 0x20, ''// {{Template:Unverified|Legion+}}''<br />
mddf_entry_is_filedata_id = 0x40, ''// {{Template:Unverified|Legion+}} nameId is a file data id to directly load''<br />
mddf_unk_100 = 0x100, ''// {{Template:Unverified|Legion+}}''<br />
};<br />
struct SMDoodadDef {<br />
/*0x00*/ uint32_t nameId; ''// references an entry in the [[ADT#MMID_chunk|MMID]] chunk, specifying the model to use.''<br />
''// if flag mddf_entry_is_filedata_id is set, a file data id instead, ignoring [[ADT#MMID_chunk|MMID]].<br />
/*0x04*/ uint32_t uniqueId; ''// this ID should be unique for all ADTs currently loaded. Best, they are unique for the whole map. Blizzard has ''<br />
''// these unique for the whole game.''<br />
/*0x08*/ {{Type|C3Vector}} position; ''// This is relative to a corner of the map. Subtract 17066 from the non vertical values and you should start to see ''<br />
''// something that makes sense. You'll then likely have to negate one of the non vertical values in whatever ''<br />
''// coordinate system you're using to finally move it into place.''<br />
/*0x14*/ {{Type|C3Vector}} rotation; ''// degrees. This is not the same coordinate system orientation like the ADT itself! (see history.)<br />
/*0x20*/ uint16_t scale; ''// 1024 is the default size equaling 1.0f.''<br />
/*0x22*/ uint16_t flags; ''// values from enum MDDFFlags.''<br />
/*0x24*/<br />
} doodadDefs[];<br />
<br />
* How to compute a matrix to map M2 to world coordinates<br />
Math is the same as for '''[[ADT#MODF_chunk|MODF]]''', only with scale being added.<br />
<br />
Example in js with gl-matrix:<br />
createPlacementMatrix : function(mddf) {<br />
var TILESIZE = 533.333333333;<br />
<br />
var posx = 32 * TILESIZE - mddf.position[0];<br />
var posy = mddf.position[1];<br />
var posz = 32 * TILESIZE - mddf.position[2];<br />
<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
<br />
mat4.rotateX(placementMatrix, placementMatrix, glMatrix.toRadian(90));<br />
mat4.rotateY(placementMatrix, placementMatrix, glMatrix.toRadian(90));<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [posx, posy, posz]);<br />
<br />
mat4.rotateY(placementMatrix, placementMatrix, glMatrix.toRadian(mddf.rotation[1] - 270));<br />
mat4.rotateZ(placementMatrix, placementMatrix, glMatrix.toRadian(-mddf.rotation[0]));<br />
mat4.rotateX(placementMatrix, placementMatrix, glMatrix.toRadian(mddf.rotation[2] - 90));<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [mddf.scale / 1024, mddf.scale / 1024, mddf.scale / 1024]);<br />
<br />
return placementMatrix;<br />
}<br />
<br />
==== Coordinate System Translation ====<br />
<br />
Here is an overview of common coordinate systems. Imagine you are a bird, looking down on the ground, oriented to the north.<br />
<br />
{| class="wikitable"<br />
! Coordinates !! Axis X !! Axis Y !! Axis Z !! Orientation !! Vector !! Remarks<br />
|-<br />
| WDT/ADT (Terrain) || North ← South || West ← East || Up || RH || <tt>Vector3.Forward * x + Vector3.Left * y + Vector3.Up * z</tt> ||<br />
|-<br />
| M2/WMO (Models) || North → South || West → East || Up || RH || <tt>Vector3.Backward * x + Vector3.Right * y + Vector3.Up * z</tt> ||<br />
|- style="vertical-align:top;"<br />
| MDDF/MODF (Placement) || West ← East || Up || North ← South || RH || <tt>Vector3.Left * x' + Vector3.Up * y + Vector3.Forward * z'<br/>&nbsp;&nbsp;Rotation x: around West/East axis<br/>&nbsp;&nbsp;Rotation y: around Up axis<br/>&nbsp;&nbsp;Rotation z: around North/South axis<br/>&nbsp;&nbsp;&nbsp;&nbsp;''for LH renderers, all rotations have to be negated (made anti-clockwise)''</tt> || <tt>x' = 32 * TILESIZE - x ; z' = 32 * TILESIZE - z</tt><br />
|-<br />
! Renderer !! Axis X !! Axis Y !! Axis Z !! Orientation !! Vector Definition !! Remarks<br />
|-<br />
| Blender || West → East || North ← South || Up || RH || <tt>Vector3.Right = (1,0,0) ; Vector3.Forward = (0,1,0) ; Vector3.Up = (0,0,1)</tt> || <tt>Vector.Left = -Vector.Right ; ...</tt><br />
|-<br />
| Unreal || West → East || North → South || Up || LH || <tt>Vector3.Right = (1,0,0) ; Vector3.Backward = (0,1,0) ; Vector3.Up = (0,0,1)</tt> ||<br />
|-<br />
| Unity || West → East || Up || North ← South || LH || <tt>Vector3.Right = (1,0,0) ; Vector3.Up = (0,1,0) ; Vector3.Forward = (0,0,1)</tt> ||<br />
|-<br />
| Direct3D || West → East || Up || North ← South || LH || <tt>Vector3.Right = (1,0,0) ; Vector3.Up = (0,1,0) ; Vector3.Forward = (0,0,1)</tt> ||<br />
|-<br />
| OpenGL (WebGL) || West → East || Up || North → South || RH || <tt>Vector3.Right = (1,0,0) ; Vector3.Up = (0,1,0) ; Vector3.Backward = (0,0,1)</tt> ||<br />
|-<br />
| Vulkan || West → East || Up || North → South || RH || <tt>Vector3.Right = (1,0,0) ; Vector3.Up = (0,1,0) ; Vector3.Backward = (0,0,1)</tt> ||<br />
|}<br />
<br />
''How do I read this table?''<br />
: Every 3D renderer, be it a modelling software, a game engine, or a 3D rendering framework, defines its own axes and orientation. This was done back in the days, when companies wanted to be as incompatible to another as possible. Invert an axis, swap two axes and importing/exporting without pre- and post processing will become impossible. Or require expensive conversion tools. History. In general, there are 2 systems with either Y-up, or Z-up and both can be either left-, or right-handed. Blizzard uses a Z-up, left handed coordinate system with swapped x- and y-axis. This coordinate system is also used by the WDT/ADT terrain system. But more importantly, it is the coordinate system between the world servers and the clients to position players, NPCs, all game objects in the world. A 3D vector with the 3 components x, y, and z is positioned inside the game as follows: The x-coordinate lies on the north-south axis with values increasing going to the north. North hereby is the actual north orientation inside the game. Similarly, the y-coordinate lies on the west-east axis with values increasing going to the west. The z-coordinate defines the height above the ground level. Blizzard also uses a yard-based system, 1 unit represents 1 yard. <br />
<br />
: Based on that orientation, the table gives a transformation into other renderers, while keeping directions intact. The north-south axis defines a forward/backward direction. The west-east axis defines a left/right direction. It is defined by the renderer if these axes are labelled x, y, z and if any are negated (going north to south, or going south to north). The table shows, what each renderer defines as its x-, y- and z-axis. Select the correct unit vectors, take the vector expression, and as a result you get the transformed vector that has the correct orientation inside the renderer. That means, for example in Unity or Blender, all models will be correctly positioned to the diverse left-view, right-view, top-view and all the other views.<br />
<br />
== MODF chunk ==<br />
* split files: obj<br />
*'''Placement information for [[WMO]]s.''' Additional to this, the WMOs to render are referenced in each [[ADT/v18#MCRF_sub-chunk|MCRF]] chunk. ''(?)''<br />
enum MODFFlags {<br />
modf_destroyable = 0x1, ''// set for destroyable buildings like the tower in DeathknightStart. This makes it a server-controllable game object.''<br />
modf_use_lod = 0x2, ''// WoD(?)+: also load _LOD1.WMO for use dependent on distance''<br />
modf_unk_has_scale = 0x4, ''// Legion+: if this flag is set then use scale = scale / 1024, otherwise scale is 1.0<br />
modf_entry_is_filedata_id = 0x8 ''// Legion+: nameId is a file data id to directly load //SMMapObjDef::FLAG_FILEDATAID<br />
modf_use_sets_from_mwds = 0x80 ''// Shadowlands+: if set, doodad set indexes of which to load should be taken from MWDS chunk<br />
};<br />
struct SMMapObjDef {<br />
uint32_t nameId; ''// references an entry in the [[ADT#MWID_chunk|MWID]] chunk, specifying the model to use.''<br />
uint32_t uniqueId; ''// this ID should be unique for all ADTs currently loaded. Best, they are unique for the whole map.''<br />
{{Type|C3Vector}} position;<br />
{{Type|C3Vector}} rotation; ''// same as in [[ADT#MDDF_chunk|MDDF]].''<br />
{{Type|CAaBox}} extents; ''// position plus the transformed wmo bounding box. used for defining if they are rendered as well as collision.''<br />
uint16_t flags; ''// values from enum MODFFlags.''<br />
uint16_t doodadSet; ''// which WMO doodad set is used. Traditionally references [[WMO#MODS_chunk]], if modf_use_sets_from_mwds is set, references [[#MWDR_.28Shadowlands.2B.29]]''<br />
uint16_t nameSet; ''// which WMO name set is used. Used for renaming goldshire inn to northshire inn while using the same model.''<br />
uint16_t scale; ''// Legion+: scale, 1024 means 1 (same as MDDF). Padding in 0.5.3 alpha. <br />
} mapObjDefs[0];<br />
<br />
* How to compute a matrix to map WMO to world coordinates<br />
The position field in MODF is in Y-up coordinate system with upper-left corner being (0,0). And when you move to the right or down in this system the values increases.<br><br />
While in WoW world coordinates are in Z-up order with the top-left corner being (17.066, 17,066) and when you move to left or down - the values decreases.<br><br />
So to get a proper positioning you need to translate those values to world coordinate system by substracting them x and z (index 0 and 2 in position array) from 17,066.<br />
<br />
The rotation field is given in degrees. You would need to translate it into radians before passing to rotate function.<br />
<br />
Example implementation in js with gl-matrix library[https://github.com/toji/gl-matrix]:<br />
function createPlacementMatrix(modf){<br />
var TILESIZE = 533.333333333;<br />
<br />
var posx = 32*TILESIZE - modf.position[0];<br />
var posy = modf.position[1];<br />
var posz = 32*TILESIZE - modf.position[2];<br />
<br />
<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
<br />
//Rotate coordinate system into Z-up<br />
mat4.rotateX(placementMatrix, placementMatrix, glMatrix.toRadian(90));<br />
mat4.rotateY(placementMatrix, placementMatrix, glMatrix.toRadian(90));<br />
<br />
//Translate the center of coordinate system<br />
mat4.translate(placementMatrix, placementMatrix, [posx, posy, posz]);<br />
<br />
// Rotate the coordinates<br />
mat4.rotateY(placementMatrix, placementMatrix, glMatrix.toRadian(modf.rotation[1]-270));<br />
mat4.rotateZ(placementMatrix, placementMatrix, glMatrix.toRadian(-modf.rotation[0]));<br />
mat4.rotateX(placementMatrix, placementMatrix, glMatrix.toRadian(modf.rotation[2]-90));<br />
<br />
return placementMatrix;<br />
}<br />
<br />
To get WMO vertexes into world position, you would need to multiply this matrix by 4-component vertex vector from left, with index 0-2 being x, y, z and index 3 being 1.<br />
<br />
placementMatrix * (x, y, z, 1)<br />
<br />
Example multiplication in js with gl-matrix:<br />
<br />
function translate (position, positionMatrix) {<br />
var position4 = vec4.fromValues(position[0], position[1], position[2], 1);<br />
vec4.transformMat4(position4 , position4 , positionMatrix);<br />
return position4;<br />
}<br />
<br />
For rendering, it is recommended to make this transformation in shader. So you would not have dublicate vertex data in gpu memory. <br />
Example in glsl:<br />
attribute vec3 aPosition;<br />
uniform mat4 uPlacementMat;<br />
<br />
void main() {<br />
vec4 worldPoint = uPlacementMat * vec4(aPosition, 1);<br />
gl_Position = worldPoint; <br />
}<br />
<br />
== MH2O chunk (WotLK+) ==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=3|note=Replacement for [[#MCLQ_sub-chunk|MCLQ]]}}<br />
* split files: root<br />
Replacement for [[#MCLQ_sub-chunk|MCLQ]], which is still parsed by the client for backwards compatibility.<br />
<br />
The chunk is seperated in three parts: A header (<tt>SMLiquidChunk</tt>), the data-block (<tt>SMLiquidInstance</tt>) and the referenced data from both. Reading it all at once and then using the supplied offsets inside is recommended. All offsets are relative to the data begin of the chunk. The header is the only part with a guaranteed position. All other parts are specified by offsets. <br />
<br />
===header===<br />
The header is a list of 256 entries:<br />
struct SMLiquidChunk {<br />
uint32_t offset_instances; // points to SMLiquidInstance[layer_count]<br />
uint32_t layer_count; // 0 if the chunk has no liquids. If > 1, the offsets will point to arrays.<br />
uint32_t offset_attributes; // points to mh2o_chunk_attributes, can be ommitted for all-0<br />
} chunks[16*16];<br />
<br />
===attributes===<br />
<tt>chunks[].offset_attributes</tt> points to one of<br />
struct mh2o_chunk_attributes {<br />
uint64_t fishable; // seems to be usable as visibility information.<br />
uint64_t deep; // TC: treat as fatigue area if bit set<br />
};<br />
Note that these are <tt>8*8</tt> bit masks.<br />
<br />
===instances===<br />
<tt>chunks[].offset_instances</tt> points to <tt>chunks[].layer_count</tt> entries of<br />
struct SMLiquidInstance {<br />
{{Template:Type/foreign_key|type=uint16_t|table=LiquidType}} liquid_type;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3}} <br />
uint16_t LVF; // LiquidVertexFormat, used in [[ADT/v18#instance_vertex_data]]<br />
#else<br />
{{Template:Type/foreign_key|type=uint16_t|table=LiquidObject}} liquid_object_or_lvf; // if >= 42, look up via {{Template:DBField|table=LiquidObject|column=LiquidTypeID}} => {{Template:DBField|table=LiquidType|column=MaterialID}} => {{Template:DBField|table=LiquidMaterial|column=LVF}}, otherwise <tt>LVF</tt><br />
// also see below for offset_vertex_data: if that's 0 and lt ≠ 2 → lvf = 2<br />
#endif<br />
float min_height_level; // used as height if no heightmap given {{Template:Unverified|and culling}}<br />
float max_height_level; // {{Template:Unverified|1={{Template:Sandbox/VersionRange|min_expansionlevel=6}} ignores value and assumes to both be 0.0 for LVF = 2!}}<br />
uint8_t x_offset; // The X offset of the liquid square (0-7)<br />
uint8_t y_offset; // The Y offset of the liquid square (0-7)<br />
uint8_t width; // The width of the liquid square (1-8)<br />
uint8_t height; // The height of the liquid square (1-8)<br />
// The above four members are only used if liquid_object_or_lvf <= 41. Otherwise they are assumed 0, 0, 8, 8. (18179) <br />
uint32_t offset_exists_bitmap; // not all tiles in the instances need to be filled. always (width * height + 7) / 8 bytes.<br />
// offset can be 0 for all-exist. also see (and extend) [[Talk:ADT/v18#SMLiquidInstance]]<br />
uint32_t offset_vertex_data; // actual data format defined by {{Template:DBField|table=LiquidMaterial|column=m_LVF}} via {{Template:DBField|table=LiquidType|column=m_materialID}}<br />
// if offset = 0 and liquidType ≠ 2, then let LVF = 2, i.e. some ocean shit<br />
};<br />
====LiquidObject shit====<br />
This is very horrible writedown of <tt>Liquid::RegisterLiquidObject</tt>. <br />
* <tt>LO < 42 || !exists (LO)</tt><br />
** <tt>LM = 2</tt> → <tt>useTexCoordLiquidObject</tt><br />
** <tt>LM = 1</tt> → <tt>oceanLiquidObject</tt>, but if <tt>LT ≠ 2 && LT ≠ 14</tt> → <tt>usePlanarMapLiquidObject</tt>, but if <tt>LT == 17</tt> → <tt>usePlanarMapLiquidObjectNoSky</tt><br />
** <tt>LM = 0</tt> → <tt>usePlanarMapLiquidObject</tt>, but if <tt>LM = 5 && (LT = 350 || LT = 412)</tt> → <tt>usePlanarMapLiquidObjectNoSky</tt><br />
** {{Template:Unverified|Legion has more than <tt>0, 1, 2 LM</tt>. This is based on 18179.}} <br />
* <tt>LO = 42 || LT = 14</tt> → <tt>oceanLiquidObject</tt><br />
* otherwise <tt>LO</tt> is in DB, take that.<br />
<br />
=====Alternate case determination=====<br />
There is a somewhat better way to identify the case 0..3 without the need of .dbc files. <br />
<br />
Data in this chunk is always organized as this:<br />
<br />
* <tt>SMLiquidChunk[]</tt> → 16x16 (256)<br />
* <tt>SMLiquidInstance[]</tt> <br />
* byte[] → <tt>SMLiquidData</tt><br />
<br />
These are the offsets, relative to the start of the chunk:<br />
<br />
* <tt>SMLiquidChunk[]</tt> → starts at chunk offset <tt>0</tt><br />
* <tt>SMLiquidInstance[]</tt> → starts at chunk offset <tt>256 * sizeof(SMLiquidChunk)</tt><br />
* <tt>SMLiquidData</tt> → you need the array count of <tt>SMLiquidInstance[]</tt><br />
** All <tt>SMLiquidData</tt> offsets are given in the fields <tt>SMLiquidChunk.offset_attributes</tt>, and <tt>SMLiquidInstance.offset_exists_bitmap</tt>, and <tt>SMLiquidInstance.offset_vertex_data</tt>.<br />
** To determine the array count of <tt>SMLiquidInstance[]</tt>, use the first <tt>SMLiquidData</tt> offset you determine when parsing. <br />
** The byte difference between this first <tt>SMLiquidData</tt> and the start of <tt>SMLiquidInstance[]</tt> should be a multiple of <tt>sizeof(SMLiquidInstance)</tt>, and when divided by, give the array count of <tt>SMLiquidInstance[]</tt><br />
** There is no padding, there are no holes in <tt>SMLiquidData</tt>. All data is written in the same order it is addressed by enumerating <tt>SMLiquidChunk[]</tt> and then sub-enumerating <tt>SMLiquidInstance[]</tt><br />
** Make a sorted list of all these offsets.<br />
<br />
When trying to read vertex data in <tt>SMLiquidInstance</tt>, you have the current <tt>SMLiquidInstance.offset_vertex_data</tt>.<br />
# Look for the next <tt>SMLiquidData</tt> offset in the sorted list. <br />
# The difference is the size of the vertex data. It should be a multiple of the vertex count.<br />
# The vertex count of each array is known with <tt>(width + 1) * (height + 1)</tt>. <br />
# This gives a combined record size of all vertex data arrays. <br />
# Divide the total size of vertex data by the vertex count, and you get a multiplier:<br />
#* multiplier 5 (float, char) => <b>case 0</b><br />
#* multiplier 8 (float, 2x u16) => <b>case 1</b><br />
#* multiplier 1 (char) => <b>case 2</b><br />
#* multiplier 9 (float, 2x u16, char) => <b>case 3</b><br />
<br />
This is working for all known maps. While this is not the most elegant way, it provides an easy determination of the used vertex data structures without the need of .dbc files, or special cases that may be added in the client's code.<br />
<br />
===instance vertex data===<br />
Regardless of LiquidVertexFormat (LVF), the arrays will always have <tt>(width + 1) * (height + 1)</tt> entries. For layout, see LiquidVertexFormat cases below.<br />
<br />
* no <tt>heightmap</tt> means that <tt>min/max_height_level</tt> is used for all points.<br />
** case 2 is always at 0.0, not <tt>*_height_level</tt>!<br />
* <tt>depthmap</tt> values are mapped to <tt>[0.0 1.0]</tt> for the shaders.<br />
<br />
struct uv_map_entry {<br />
uint16_t x; // divided by 8 for shaders<br />
uint16_t y;<br />
};<br />
<br />
====Case 0, Height and Depth data====<br />
This is the go-to layout for pre-WoD (MoP?) data. <br />
struct {<br />
float heightmap[];<br />
char depthmap[];<br />
};<br />
<br />
====Case 1, Height and Texture Coordinate data====<br />
struct {<br />
float heightmap[];<br />
uv_map_entry uvmap[];<br />
}<br />
<br />
I couldn't get the UV coordinates to make sense so I ended up disabling them. -- Rour<br />
<br />
====Case 2, Depth only data====<br />
The liquid's height is always 0.0 regardless of the <tt>liquid_type</tt> or <tt>*_height_level</tt>!<br />
struct {<br />
char depthmap[];<br />
}<br />
<br />
====Case 3, Height, Depth and Texture Coordinates====<br />
struct {<br />
float heightmap[];<br />
uv_map_entry uvmap[];<br />
char depthmap[];<br />
}<br />
<br />
===example, notes===<br />
The full heightmap that covers a whole chunk would be created from 9x9 float values, effectively creating 8x8 quadratic pieces. But since WotLK and the introduction of the MH2O chunk there is no more need to define the full heightmap if only part of a chunk is actually covered with water (such as with a thin river). Instead, MH2O_Information.x, .y, .width and .height define the size and location of a "liquid rectangle" which can be smaller than a full chunk.<br />
<br />
An example: let's say there's a river crossing a chunk like this ('x' is the river):<br />
<br />
++++++++<br />
++++++++<br />
xxxxxx++<br />
++xxxxxx<br />
++++++++<br />
++++++++<br />
++++++++<br />
++++++++<br />
<br />
This would lead to <tt>x_offset</tt> = 0, <tt>y_offset</tt> = 2, <tt>width</tt> = 8 and <tt>height</tt> = 2. The data at <tt>vertex_data.heightmap</tt> would then list 27 float values for the height map (a 9x3 height map which results in 8x2 quads, as shown in the picture above).<br />
<br />
The data pointed to by <tt>offset_exists_bitmap</tt> would finally define which of the quads should be rendered. Its length is just enough to cover the parts of the chunk that contain liquids. In the above example that would be 2x8 liquid tiles => 16 bits => 2 bytes. In binary (left to right) the values would be 11111100 00111111 or the two byte values 0x3F and 0xFC.<br />
<br />
Note that it is always possible to omit <tt>offset_exists_bitmap</tt> and/or <tt>offset_vertex_data</tt> to save some bytes in the ADT file! If <tt>offset_attributes</tt> is not given, the whole liquid instance is to be rendered. If <tt>offset_vertex_data</tt> is not given, then the height map consists only of values equal to heightLevel1 (I am not 100% sure of this one, but this approach seems to work fine for me).<br />
<br />
== MCNK chunk ==<br />
* split files: header in root, no header in obj and tex<br />
<br />
*'''After the above mentioned chunks come 256 individual MCNK chunks, row by row, starting from top-left (northwest).''' The MCNK chunks have a large block of data that starts with a header, and then has sub-chunks of its own.<br />
<br />
Each map chunk has 9x9 vertices, and in between them 8x8 additional vertices, several texture layers, normal vectors, a shadow map, etc.<br />
<br />
'''Important: Offsets to sub-chunks are relative to the beginning of MCNK chunk, not MCNK chunk data.'''<br />
<br />
The [[ADT#MCNK_chunk|MCNK]] header is 128 bytes large.<br />
<br />
struct SMChunk<br />
{<br />
struct<br />
{<br />
uint32_t has_mcsh : 1;<br />
uint32_t impass : 1;<br />
uint32_t lq_river : 1;<br />
uint32_t lq_ocean : 1;<br />
uint32_t lq_magma : 1;<br />
uint32_t lq_slime : 1;<br />
uint32_t has_mccv : 1;<br />
uint32_t unknown_0x80 : 1;<br />
uint32_t : 7; // not set in 6.2.0.20338<br />
uint32_t do_not_fix_alpha_map : 1; // "fix" alpha maps in MCAL ''and MCSH'' (4 bit alpha maps are 63*63 instead of 64*64).<br />
// If this flag is not set, the MCAL format *has* to be unfixed4444, otherwise UnpackAlphaShadowBits will assert.<br />
uint32_t high_res_holes : 1; // Since ~5.3 WoW uses full 64-bit to store holes for each tile if this flag is set.<br />
uint32_t : 15; // not set in 6.2.0.20338<br />
} flags;<br />
<br />
/*0x004*/ uint32_t IndexX;<br />
/*0x008*/ uint32_t IndexY;<br />
#if version < ?<br />
float radius;<br />
#endif<br />
/*0x00C*/ uint32_t nLayers; // maximum 4<br />
/*0x010*/ uint32_t nDoodadRefs;<br />
#if version >= ~5.3<br />
uint64_t holes_high_res; // only used with flags.high_res_holes<br />
#else<br />
/*0x014*/ uint32_t [[ADT#MCVT_sub-chunk|ofsHeight]];<br />
/*0x018*/ uint32_t [[ADT#MCNR_sub-chunk|ofsNormal]];<br />
#endif<br />
/*0x01C*/ uint32_t [[ADT#MCLY_sub-chunk|ofsLayer]];<br />
/*0x020*/ uint32_t [[ADT#MCRF_sub-chunk|ofsRefs]];<br />
/*0x024*/ uint32_t [[ADT#MCAL_sub-chunk|ofsAlpha]];<br />
/*0x028*/ uint32_t sizeAlpha;<br />
/*0x02C*/ uint32_t [[ADT#MCSH_sub-chunk|ofsShadow]]; // only with flags.has_mcsh<br />
/*0x030*/ uint32_t sizeShadow;<br />
/*0x034*/ uint32_t areaid; // in alpha: both zone id and sub zone id, as uint16s.<br />
/*0x038*/ uint32_t nMapObjRefs;<br />
/*0x03C*/ uint16_t holes_low_res;<br />
/*0x03E*/ uint16_t unknown_but_used; // in alpha: padding<br />
/*0x040*/ uint2_t[8][8] ReallyLowQualityTextureingMap; // "predTex", It is used to determine which detail doodads to show. Values are an array of two bit <br />
// unsigned integers, naming the layer.<br />
/*0x050*/ uint1_t[8][8] noEffectDoodad; // doodads disabled if 1; WoD: may be an explicit MCDD chunk<br />
/*0x058*/ uint32_t [[ADT#MCSE_sub-chunk|ofsSndEmitters]];<br />
/*0x05C*/ uint32_t nSndEmitters; // will be set to 0 in the client if ofsSndEmitters doesn't point to [[ADT#MCSE_sub-chunk|MCSE]]!<br />
/*0x060*/ uint32_t [[ADT#MCLQ_sub-chunk|ofsLiquid]];<br />
/*0x064*/ uint32_t sizeLiquid; // 8 when not used; only read if >8.<br />
<br />
// in alpha, remainder is padding but unused.<br />
<br />
/*0x068*/ {{Template:Type|C3Vector}} position;<br />
/*0x074*/ uint32_t [[ADT#MCCV_sub-chunk|ofsMCCV]]; // only with flags.has_mccv, had uint32_t textureId; in ObscuR's structure.<br />
/*0x078*/ uint32_t [[ADT#MCLV_sub-chunk|ofsMCLV]]; // introduced in Cataclysm<br />
/*0x07C*/ uint32_t unused; // currently unused<br />
/*0x080*/<br />
};<br />
<br />
=== Terrain Holes ===<br />
<br />
About the holes in the terrain: This is a bitmapped field, the least significant 16 bits are used row-wise in the following arrangement with a 1 bit meaning that the map chunk has a hole in that part of its area:<br />
0x1 0x2 0x4 0x8<br />
0x10 0x20 0x40 0x80<br />
0x100 0x200 0x400 0x800<br />
0x1000 0x2000 0x4000 0x8000<br />
<br />
Since approx. 5.3, WoW uses a new 64-bit hole map if needed. If so, flag high_res_holes is set in the MCNK header and the 8 bytes at offset chunkBegin+0x14 (ofsHeight and ofsNormal) contain the hole map. Otherwise, the low resolution 16-bit hole map is used. See MapChunk::CreatePointers and/or [http://www.ownedcore.com/forums/world-of-warcraft/world-of-warcraft-bots-programs/wow-memory-editing/409718-navmesh-mpq-geometry-parsing-issues.html#post2757020 this post].<br />
Read those 8 bytes as byte array and check for holes like (Holes[row] >> col) & 1. If you interpret it as an uint64_t and shift like you did before on the 16-bit map, you have to invert the rows because of endianness.<br />
<br />
=== MCVT sub-chunk ===<br />
* split files: root<br />
<br />
struct <br />
{<br />
float height[9*9 + 8*8];<br />
} mcvt;<br />
<br />
These are the actual height values for the 9x9+8x8 vertices. 145 floats in the following order/arrangement:. '''The values in here are only relative to the position given in the corresponding MCNK chunk.'''<br />
1 2 3 4 5 6 7 8 9<br />
10 11 12 13 14 15 16 17<br />
18 19 20 21 22 23 24 25 26<br />
27 28 29 30 31 32 33 34<br />
35 36 37 38 39 40 41 42 43<br />
44 45 46 47 48 49 50 51<br />
52 53 54 55 56 57 58 59 60<br />
61 62 63 64 65 66 67 68<br />
69 70 71 72 73 74 75 76 77<br />
78 79 80 81 82 83 84 85<br />
86 87 88 89 90 91 92 93 94<br />
95 96 97 98 99 100 101 102<br />
103 104 105 106 107 108 109 110 111<br />
112 113 114 115 116 117 118 119<br />
120 121 122 123 124 125 126 127 128<br />
129 130 131 132 133 134 135 136<br />
137 138 139 140 141 142 143 144 145<br />
<br />
<del>The inner 8 vertices are only rendered in WoW when its using the up-close LoD. Otherwise, it only renders the outer 9.</del> Nonsense? If I only change one of these it looks like: [[Media:WoWScrnShot_022409_204540.jpg]].<br />
<br />
Ok, after a further look into it, WoW uses Squares out of 4 of the Outer(called NoLoD)-Vertices with one of the Inner(called LoD)-Vertices in the Center:<br />
1 2<br />
10<br />
18 19<br />
So to render them in OpenGL you can use something like this:<br />
<br />
gl.glBegin(GL.GL_TRIANGLE_STRIP);<br />
for(int x=0;x<8;x++){<br />
for(int y=0;y<8;y++){<br />
float nL1=mcvt.getValNoLOD(x, y);<br />
float nL2=mcvt.getValNoLOD(x, y+1);<br />
float nL3=mcvt.getValNoLOD(x+1, y);<br />
float nL4=mcvt.getValNoLOD(x+1, y+1);<br />
float L=mcvt.getValLOD(x, y);<br />
<br />
gl.glVertex3f( y, x, nL1);<br />
gl.glVertex3f( y+1, x, nL2);<br />
gl.glVertex3f(y+0.5f, x+0.5f, L);<br />
<br />
gl.glVertex3f( y, x, nL1);<br />
gl.glVertex3f( y, x+1,nL3);<br />
gl.glVertex3f(y+0.5f, x+0.5f,L);<br />
<br />
gl.glVertex3f( y, x+1, nL3);<br />
gl.glVertex3f( y+1, x+1, nL4);<br />
gl.glVertex3f(y+0.5f, x+0.5f,L);<br />
<br />
gl.glVertex3f( y+1, x,nL2);<br />
gl.glVertex3f( y+1, x+1, nL4);<br />
gl.glVertex3f(y+0.5f, x+0.5f, L);<br />
<br />
}<br />
} <br />
gl.glEnd();<br />
Although it seems there is still a mistake :/<br />
--[[user:Tigurius|Tigurius]]<br />
<br />
Old ones:<br />
<br />
To stripify try this one: ( stripsize is now : 16*18 + 7*2 + 8*2 )<br />
<br />
void stripify(V *in, V *out)<br />
{<br />
for (int row=0; row<8; row++) { <br />
V *thisrow = &in[row*9*2];<br />
V *nextrow = &in[row*9*2 + 9];<br />
V *overrow = &in[(row+1)*9*2];<br />
if (row>0) *out++ = thisrow[0];// jump end<br />
for (int col=0; col<8; col++) {<br />
*out++ = thisrow[col];<br />
*out++ = nextrow[col];<br />
}<br />
*out++ = thisrow[8];<br />
*out++ = overrow[8];<br />
*out++ = overrow[8];// jump start<br />
*out++ = thisrow[0];// jump end<br />
*out++ = thisrow[0];<br />
for (int col=0; col<8; col++) {<br />
*out++ = overrow[col];<br />
*out++ = nextrow[col];<br />
}<br />
if (row<8) *out++ = overrow[8];<br />
if (row<7) *out++ = overrow[8];// jump start<br />
}<br />
}<br />
<br />
or try this one (made by tharo)<br />
<br />
// to make it not TOO complicated u get data as 9*9 and 8*9 chain. <br />
// the 9th value is never used but calculation is more easy now ^^<br />
private int stripify(Point3d[] in, Point3d[] out) {<br />
int outc=0;<br />
<br />
for (int row=0; row<8; row++) {<br />
int thisrow = row*9*2;<br />
int nextrow = row*9*2 + 9;<br />
int overrow = (row+1) *9*2;<br />
<br />
for(int col=0; col<8; col++) {<br />
out[outc++] = in[thisrow+col];<br />
out[outc++] = in[nextrow+col]; <br />
}<br />
out[outc++] = in[thisrow+8];<br />
<br />
for(int col=8; col>0; col--) {<br />
out[outc++] = in[overrow+col];<br />
out[outc++] = in[nextrow+col-1]; <br />
}<br />
out[outc++] = in[overrow];<br />
out[outc++] = in[thisrow];<br />
out[outc++] = in[nexttow];<br />
out[outc++] = in[overrow];<br />
}<br />
for(int row=8; row>=0; row--) {<br />
out[outc++] = in[row*9*2];<br />
} <br />
return outc;<br />
}<br />
<br />
These points look like they might be better organized as a triangle fan instead of a strip. This is my untested guess:<br />
float wowData[145];<br />
int off = 9;<br />
float x, y;<br />
<br />
for (y = 0; y < 8; ++y, off += 9)<br />
{<br />
for (x = 0; x < 8; ++x, ++off)<br />
{<br />
glBegin(GL_TRIANGLE_FAN);<br />
glVertex3f(x, y, wowData[off]);<br />
glVertex3f(x - 0.5f, y - 0.5f, wowData[off - 9]);<br />
glVertex3f(x + 0.5f, y - 0.5f, wowData[off - 8]);<br />
glVertex3f(x + 0.5f, y + 0.5f, wowData[off + 9]);<br />
glVertex3f(x - 0.5f, y + 0.5f, wowData[off + 8]);<br />
glVertex3f(x - 0.5f, y - 0.5f, wowData[off - 9]);<br />
glEnd();<br />
}<br />
}<br />
--[[user:Kelmar|Kelmar]]<br />
<br />
=== MCLV sub-chunk (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* split files: root<br />
struct<br />
{<br />
{{Template:Type|CArgb}} values[9*9 + 8*8]; // or rgba?<br />
} chunk_lighting;<br />
<br />
Alpha is apparently ignored. Heavily used in Deepholm. In contrast to MCCV does not only color but also [[Media:Mjo_mclv.jpg|lightens up the vertices]].<br />
<br />
These are the result of baking level-designer placed omni lights. With {{Template:Sandbox/PrettyVersion|expansionlevel=6}}, they added the actual lights in [[WDT#lgt|_lgt.wdt]]s to do live lighting also influencing the character and shadow.<br />
<br />
=== MCCV sub-chunk (WotLK+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=3}}<br />
* split files: root<br />
*'''This is used for vertex shading.''' You can manipulate the color of the vertices by adding this layer of colors blended onto the terrain. You can see the effects of this in [http://www.youtube.com/watch?v=3FjuEPnuKtU this video] (see 3:25 to 3:45) from Blizzcon 09. Additionally, there is a [[Media:WoWScrnShot_092409_003328.jpg|screenshot]] showing some of the effects possible. <br />
<br />
struct MCCV {<br />
struct MCCVEntry {<br />
uint8_t blue; ''// these values range from 0x00 to 0xFF with 0x7F being the default.''<br />
uint8_t green; ''// you can interpret the values as 0x7F being 1.0 and these values being multiplicated with the vertex colors.''<br />
uint8_t red; ''// setting all values to 0x00 makes a chunk completely black.''<br />
uint8_t alpha; ''// seems not to have any effect.''<br />
} entries[9*9+8*8];<br />
};<br />
<br />
<strike>Probably argb, not rgba? --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 17:05, 26 July 2015 (UTC)</strike><br />
<br />
in WotLK the client uses bgra --[[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]) 02:12, 22 May 2016 (CEST)<br />
<br />
=== MCNR sub-chunk ===<br />
* split files: root<br />
*'''Normal vectors for each corresponding vector above.''' Its followed by some weird unknown data which is not included in the chunk itself and might be some edge flag bitmaps.<br />
<br />
struct SMNormal {<br />
struct MCNREntry {<br />
int8_t normal[3]; ''// normalized. X, Z, Y. 127 == 1.0, -127 == -1.0.''<br />
} entries[9*9+8*8];<br />
uint8_t padding[13]; ''// this data is not included in the MCNR chunk but additional data which purpose is unknown. 0.5.3.3368 lists this as padding<br />
''// '''always''' 0 112 245 18 0 8 0 0 0 84 245 18 0. Nobody yet found a different pattern. The data is '''not''' derived from the normals.''<br />
''// It also does not seem that the client reads this data. --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 23:01, 26 July 2015 (UTC)''<br />
''// While stated that this data is not "included in the MCNR chunk", the chunk-size defined for the MCNR chunk '''does''' cover this data. --[[User:Kruithne|Kruithne]] Feb 2016''<br />
''// ... from Cataclysm only (on LK files and before, MCNR defined size is 435 and not 448) [[User:Mjollna|Mjollna]] ([[User talk:Mjollna|talk]])<br />
};<br />
<br />
''Note: The normal vectors don't always have a length of 1. The value is close enough with the intend to be always 1, but the error is as high as 7 epsilon (1 epsilon is the error caused by the fixed signed 8-bit value over all 3 coordinates). With a resolution of 1.5 degrees per coordinate unit (180°/127) this already causes some lighting glitches in the terrain. As I've never encountered a value of +/-127 for the X and Y coordinate, the data writer seems to use truncation instead of rounding for both coordinates, but rounding for the Z coordinate. The Z-coordinate is always positive (always pointing up/outwards). That behavior would account for the found epsilon errors. Maybe it's best to recalculate Z from X and Y. (float Z = sqrt(1 - (X / 127)² - (Y / 127)²), Z >= 0). I don't know if the client re-normalizes vectors in code or in shaders. --[[User:Nieriel|Nieriel]] Aug 2019''<br />
<br />
=== MCLY sub-chunk ===<br />
* split files: tex<br />
''Complete and right as of 19-AUG-09 (3.0.9 or higher)''<br />
*'''Texture layer definitions for this map chunk. 16 bytes per layer, up to 4 layers (thus, layer count = size / 16).''' <br />
<br />
Every texture layer other than the first will have an alpha map to specify blending amounts. The first layer is rendered with full opacity. To know which alphamap is used, there is an offset into the [[ADT#MCAL_chunk|MCAL]] chunk. That one is relative to MCAL.<br />
<br />
You can animate these by setting the flags. Only simple linear animations are possible. You can specify the direction in 45° steps and the speed.<br />
<br />
The textureId is just the array index of the filename array in the [[ADT#MTEX_chunk|MTEX]] chunk.<br />
<br />
For getting the right feeling when walking, you should set the effectId which links to {{Template:DBField|table=GroundEffectTexture|column=m_ID}}. It defines the little detail doodads as well as the footstep sounds and if footprints are visible. You can set the id to -1 (int16!) to have no detail doodads and footsteps at all. Also, you need to define the currently on-top layer in the MCNK structure for the correct detail doodads to show up!<br />
<br />
Introduced in Wrath of the Lich King, terrain can now reflect a skybox. This is used for icecubes made out of ADTs to reflect something. You need to have the [[#MTXF_chunk_.28WotLK.2B.29|MTXF]] chunk in, if you want that. Look at an skybox Blizzard made to see how you should do it.<br />
<br />
struct SMLayer<br />
{<br />
uint32_t textureId; <br />
struct<br />
{<br />
uint32_t animation_rotation : 3; // each tick is 45°<br />
uint32_t animation_speed : 3; <br />
uint32_t animation_enabled : 1;<br />
uint32_t overbright : 1; // This will make the texture way brighter. Used for lava to make it "glow".<br />
uint32_t use_alpha_map : 1; // set for every layer after the first<br />
uint32_t alpha_map_compressed : 1; // see MCAL chunk description<br />
uint32_t use_cube_map_reflection : 1; // This makes the layer behave like its a reflection of the skybox. See below<br />
uint32_t unknown_0x800 : 1; // WoD?+ if either of 0x800 or 0x1000 is set, [[#MTXF_chunk_.28WotLK.2B.29|texture effects]]' texture_scale is applied<br />
uint32_t unknown_0x1000 : 1; // WoD?+ see 0x800<br />
uint32_t : 19;<br />
} flags; <br />
uint32_t offsetInMCAL;<br />
{{Template:Type/foreign_key|table=GroundEffectTexture}} effectId; // 0xFFFFFFFF for none, in alpha: uint16_t + padding<br />
} layers[/* <= 4 */];<br />
<br />
To know how much entries there are, read until you hit the end of the chunk. Or divide it by 16 (4 + 4 + 4 + 4)<br />
<br />
'''Notes for textureId''': <br />
* {{Template:Sandbox/VersionRange|max_expansionlevel=8|max_build=8.1.0.27791}} Additinally to texture pointed by textureId, client will always try to load _s.blp texture, unless do_not_load_specular_or_height_texture_but_use_cubemap is present in MTXF/MTXP chunk. <br />
* {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} _s.blp and _h.blp are given by [[#MDID|MDID]] and [[#MHID|MHID]] rather than inferred from the filename. <br />
* Only in {{Template:Sandbox/PrettyVersion|expansionlevel=8|build=8.1.0.27826}}, [[#MTEX chunk|MTEX]] still exists though and is not replaced by an ID based chunk. <br />
* {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}}, [[#MTEX chunk|MTEX]] no more exists. The client referenced the texture from [[#MDID|MDID]] and [[#MHID|MHID]].<br />
<br />
<br />
'''Explanation for flag 0x400 (use_cube_map_reflection):'''<br />
<br />
First of all you can see the effects in this video: [http://www.youtube.com/watch?v=uOE9OIG_rFM Video] The texture that became the 0x400 flag was the following: [[Media:4b795c7c7f36b_TCB_CrystalSong_B.jpg]] . Have a look at the bright points that wander with the toon as it moves. This should imitate the stars from the sky (that you can find in the texture).<br />
<br />
There are some important things you should be aware when using the flag 0x400:<br />
*It doesnt matter for which layer you set the flag 0x400, it will always affect the groundlayer. <br />
*The common skyboxtextures need to have the same ration from width and height as the one posted above. If this isnt the case, it looks like that: [http://www.youtube.com/watch?v=YJDB6OEyxoc Video] You see that it doesnt really fit the shape.<br />
*All of the skyboxtextures blizzard has need to specify a special flag to be decompressed correctly. This is done using the [[#MTXF_chunk_.28WotLK.2B.29|MTXF]]-chunk. If the texture has a 1 in MTXF it will be interpreted correctly, else it will be green.<br />
<br />
--[[User:Cromon|Cromon]]<br />
<br />
=== MCRF sub-chunk (<Cata)===<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=3|note=Now split into [[#MCRD_.28Cata.2B.29|MCRD]] and [[#MCRW_.28Cata.2B.29|MCRW]]}}<br />
<br />
*'''A list of with MCNK.nDoodadRefs + MCNK.nMapObjRefs indices into the file's [[ADT#MDDF_chunk|MDDF]] and [[ADT#MODF_chunk|MODF]] chunks,''' saying which [[ADT#MCNK_chunk|MCNK]] subchunk those particular doodads and objects are drawn within. This [[ADT#MCRF_sub-chunk|MCRF]] list contains duplicates for map doodads that overlap areas. <br />
<br />
uint32_t doodad_refs[header.nDoodadRefs]; // into MDDF<br />
uint32_t object_refs[header.nMapObjRefs]; // into MODF<br />
<br />
The client uses those MCRF-entries to calculate collision. Only objects which are referenced in the current chunk of the toon get checked against collision (this is only for MDX, WMO seem to have different collision). If a doodad entry from MDDF or MODF gets never referenced in a chunks MCRF it wont be drawn at all, WoW doesnt take the MDDF and MODF to draw the objects. --[[User:Cromon|Cromon]]<br />
<br />
This chunk (doodads part only) and MCRD need to be sorted by size category if WDT's flag 8 is set. This is an optimisation to speed up culling. Note that size category ≠ size! The size category limits per default are <tt>1.0, 4.0, 25.0, 100.0, 100000.0</tt>. The size that is relevant is the longest side of an AABB transformed model's bounding box from the M2 header {{Template:Unverified| (it seems to be the max distance between 2 corners of the AABB)}}.<br />
<br />
<br />
The size category is also used for culling regardless of the WDT's flag, models fade when close to the max rendering distance and disappear when hitting the max rendering distance. <br />
-- [[User:Adspartan|Adspartan]]<br />
<br />
Those are approximate values for {{Template:Sandbox/PrettyVersion|expansionlevel=3}}:<br />
{| border="1"<br />
! size category limit !! max render distance<br />
|-<br />
| 1.0 || 30<br />
|-<br />
| 4.0 || 150<br />
|-<br />
| 25.0 || 300<br />
|-<br />
|}<br />
<br />
===MCRD (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4|note=Replaces MCRF. See that for details}}<br />
* split files: obj<br />
uint32_t mddf_entry[];<br />
<br />
===MCRW (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4|note=Replaces MCRF. See that for details}}<br />
* split files: obj<br />
uint32_t modf_entry[];<br />
<br />
=== MCSH sub-chunk ===<br />
* split files: tex<br />
*'''Shadow map for static shadows on the terrain.''' Can be left out with the chunk&1 flag not set.<br />
* '''Note''': The <tt> do_not_fix_alpha_map</tt> flag in MCNK also has influence on this chunk: Shadows are unfixed and fixed as well depending on that flag.<br />
<br />
struct {<br />
uint1_t shadow_map[64][64];<br />
// or 63x63 with the last column&row&cell auto-filled as detailed in MCAL.<br />
} mcsh;<br />
<br />
Thanks to Sylvain, the shadow maps work as follows: the shadows are stored per bit, not byte as 0 or 1 (off or on) so we have 8 bytes (which equates to 64 values) X 64 bytes (64 values in this case) which ends up as a square 64x64 shadowmap with either white or black. Note that the shadow values come LSB first.<br />
<br />
=== MCAL sub-chunk ===<br />
* split files: tex<br />
*'''Alpha maps for additional texture layers.''' <br />
<br />
There are 3 kinds of alpha maps here: Which one depends on [[ADT#MCLY_sub-chunk|MCLY]] (0x200) and [[WDT#MPHD_chunk|WDT's MPHD]] (0x4 and 0x80) flags.<br />
<br />
{| border="1"<br />
! [[ADT#MCLY_sub-chunk|MCLY]] !! [[WDT#MPHD_chunk|WDT's MPHD]] !! mode<br />
|-<br />
| || || Uncompressed (2048)<br />
|-<br />
| || align="center"| 0x4 or 0x80 set || Uncompressed (4096)<br />
|-<br />
| align="center"| 0x200 set || align="center"| 0x4 or 0x80 set || Compressed: MPHD is only about bit depth! <br />
|}<br />
<br />
Additionally to this, [[ADT#MCNK_chunk|MCNK]] can have (and mostly does have) the 0x8000 flag. If this flag is set and bit depth is 8 ([[WDT#MPHD_chunk|WDT's MPHD]] has either flag), then modify alpha values so that if there is shadow at the corresponding position, the alpha value is multiplied by 0.7f (178 * alpha >> 8 to be exact).<br />
<br />
==== Uncompressed (4096) ====<br />
uint8_t alpha_map[64][64];<br />
<br />
For 4096 byte chunks, just read the values straight into your alpha channel. This, again, should result in 4096 bytes for a 64px by 64px size in the final alpha map.<br />
<br />
==== Uncompressed (2048) ====<br />
uint4_t alpha_map[64][64]; // note: the client uses a 4bit alpha texture, so does not do any custom normalization. Blit_Argb4444_Abgr8888 does `value & 0xF | 0x10 * value`.<br />
<br />
Contains 2048 bytes of data, but each byte contains two values in LSB first order. This should result in a 4096 alpha map (64px by 64px).<br />
<br />
* Read a byte.<br />
* Split the byte into two 4-bit values. eg: b0101b a0101a<br />
* This results in 16 possible values for each pixel; 15 is full alpha and 0 is no alpha. If you want to normalize, use Blit_Argb4444_Abgr8888 (value & 0xF | 0x10 * value).<br />
* Record each value separately into the alpha channel in the order of a then b.<br />
<br />
'''IMPORTANT:''' Note that depending on MCNK flag FLAG_DO_NOT_FIX_ALPHA_MAP, this is not actually a 64*64 map but rather a 63*63 map with the last row and column being equivalent to the previous one. <br />
<br />
struct { uint4_t alpha_map[63]; uint4_t ignored; }[63]; <br />
uint4_t ignored[64];<br />
<br />
where <br />
<br />
alpha_map[x][63] == alpha_map[x][62]<br />
alpha_map[63][x] == alpha_map[62][x]<br />
alpha_map[63][63] == alpha_map[62][62]<br />
<br />
and all "ignored" values are ignored, while still preserving the 2048 byte footprint.<br />
<br />
* I claim that this shall be handled by saving in fixed4444 only (i.e. always set mcnk.flags FLAG_DO_NOT_FIX_ALPHA_MAP and explicitly save the "fixed" (as in have all values) version). --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 21:45, 24 October 2015 (UTC)<br />
<br />
==== Compressed ====<br />
<br />
'''Warning: can only be used when bit depth is 8'''<br />
<br />
struct<br />
{<br />
enum class mode_t<br />
{<br />
copy = 0, // append value[0..count - 1]<br />
fill = 1, // append value[0] count times<br />
};<br />
uint8_t count : 7;<br />
uint8_t mode : 1;<br />
uint8_t value[];<br />
} compressed_alpha_map[]; // size depends on content. will decompress to exactly 64*64 bytes.<br />
// minimum size is 64 times copy-64-x, thus 128 bytes<br />
// maximum size is 64 times fill-64-x[64], thus 4160 bytes<br />
<br />
* read a byte<br />
* in the first bit of that byte (sign bit) check if it's true. When true that means we are in "fill" mode, if false, "copy" mode<br />
* the next 7 bits of the byte determine how many times we "fill" or "copy" (count) (eg, max value 127 - actually 64, see notes)<br />
* fill mode: repeat the byte following the one we just read *count* number of times into the alpha map<br />
* copy mode: read *count* number of following bytes into the alpha map<br />
* repeat until the map is complete<br />
Notes:<br />
* this should result in 4096 bytes in the alpha map (64 px by 64 px)<br />
** '''This is not always the case in Blizzard ADTs!''' There are some corrupted compressed chunks that unpack to more than 4096 bytes, with no clarity where the bug happened. Noggit treats them by just stopping after 4096 bytes are unpacked. The code below does ''not'' handle this bug!<br />
* you should not have any extra compression data left over after completion<br />
* reads left to right, top to bottom<br />
* values are stored line by line, compressed_alpha_map's values cannot span over several lines or the client won't be able to read the alphamap correctly<br />
* using this format can greatly reduce the size of the ADT on disk but increase it when inside an MPQ (see below)<br />
<br />
'''Format size comparison with Northrend'''<br />
{| border="1"<br />
! Mode !! MPQ !! Disk<br />
|-<br />
| Compressed (blizzard) || 569MB || 1.07GB<br />
|-<br />
| [[ADT#Uncompressed_.284096.29|Uncompressed]] || 551MB || 2.04GB<br />
|}<br />
<br />
<br />
-- Michael Redig 25-5-2015<br />
<br />
-- [[User:Flow|Flow]] 21-10-2008<br />
<br />
=====Sample C++ code=====<br />
unsigned offI = 0; //offset IN buffer<br />
unsigned offO = 0; //offset OUT buffer<br />
char* buffIn; // pointer to data in adt file<br />
char buffOut[4096]; // the resulting alpha map<br />
<br />
while( offO < 4096 )<br />
{<br />
// fill or copy mode<br />
bool fill = buffIn[offI] & 0x80;<br />
unsigned n = buffIn[offI] & 0x7F;<br />
offI++;<br />
for( unsigned k = 0; k < n; k++ )<br />
{<br />
buffOut[offO] = buffIn[offI];<br />
offO++;<br />
if( !fill )<br />
offI++;<br />
}<br />
if( fill ) offI++;<br />
}<br />
<br />
==== Rendering ====<br />
'''Note''': Starting WoD, they added a new complication to rendering, namely height based factors from [[ADT/v18#MTXP]]. This led to the shader getting a bit extended to account for that. The new shader is '''able to render both''', _h added blending and "classic" blending. It can be found at [[ADT/v18#legion_terrain_shader_excerpt]]. The following paragraphs are left as further examples on how to render it, for versions before WoD.<br />
<br />
<br />
Blizzard has changed the way how the additional textures are blended onto the ground texture in Northrend (old continents still seem to be blended the old way; they also don't use the new alpha map format). They have gone from a "one-layer-per-step" approach to blending all the 4 textures in a single step according to the following formula:<br />
<br />
finalColor = tex0 * (1.0 - (alpha1 + alpha2 + alpha3)) + tex1 * alpha1 + tex2 * alpha2 + tex3 * alpha3<br />
<br />
So all the alpha values for the different layers including the ground layer add up to 1.0; the ground layer's alpha value is calculated to match this constraint.<br />
<br />
-- [[User:Slartibartfast|Slartibartfast]] 01-11-2008<br />
<br />
It is of course possible to devise different ways to render such terrain; one way I use and of which I know that it's working is a 2-pass-approach: first render all ground textures without blending, then use a fragment shader program to mix the 1-3 additional layer textures and render them with a glBlendFunc setting of (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) on top of the ground texture already present in the framebuffer. The fragment program that mixes the textures would have to work like this short GLSL example:<br />
<br />
gl_FragColor = texture2D(texture0, vec2(gl_TexCoord[0])) * texture2D(texture3, vec2(gl_TexCoord[3])).r<br />
+ texture2D(texture1, vec2(gl_TexCoord[1])) * texture2D(texture3, vec2(gl_TexCoord[3])).g<br />
+ texture2D(texture2, vec2(gl_TexCoord[2])) * texture2D(texture3, vec2(gl_TexCoord[3])).b;<br />
<br />
(this example uses 4 texture units: texture0 - texture3; the first 3 of them contain the actual textures, while the fourth unit contains the alpha maps combined in one RGB texture)<br />
<br />
Also, seems like alpha textures are upscaled using bicubic interpolation. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 21:49, 11 October 2015 (UTC)<br />
<br />
for code which reads through all the chunks by their size, note that the size value of the MCAL chunk is often (or always?) wrong. the 3.x client reads through the chunks by size, and when it encounters the MCAL chunk it overrides the size with the 'sizeAlpha' field in the MCNK header. it does the same for the MCLQ chunk. the 1.x client, however, reads all the MCNK chunks by their offset in the header and ignores the size.<br />
<br />
=== MCLQ sub-chunk ===<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=2|note=Deprecated, but still parsed. Replaced with [[#MH2O_chunk_.28WotLK.2B.29|MH2O]]}}<br />
* split files: root<br />
<br />
*'''Water levels for this map chunk.''' This chunk is old and not really used anymore. Still, there is backwards compatibility in the client as old ADTs are not updated as it would be much data to patch it. I guess, it will be done in some expansion. You can fully use this chunk, even to have multiple water. You can have a lot of stacked water with this and the MH2O one. <br />
*'''Deprecated with WotLK''': I advise you to implement the MH2O one as its better if you want to write a editor for ADT files. <br />
<br />
The size of the chunk is in the mapchunk header. The type of liquid is given in the mapchunk flags, also in the header.<br />
<br />
'''Note''': Presuming this chunk's data length based on <tt>IffChunk.size</tt> and/or <tt>liquidSize</tt> is unreliable; there are several cases of <tt>size != data_length</tt> and in the alpha clients there is no size indicator at all. The optimal way of parsing this chunk is to (sequentially) validate what [[#MCNK_chunk|LQ_* flags]] are set, if any, and read accordingly - this will also provide the liquid type and therefore what <tt>SLVert</tt> to use.<br />
<br />
This information is old and incomplete as well as maybe wrong. <br />
<br />
struct {<br />
{{Type|CRange}} height;<br />
struct SLVert {<br />
union {<br />
struct SWVert {<br />
char depth;<br />
char flow0Pct;<br />
char flow1Pct;<br />
char filler;<br />
float height;<br />
} waterVert;<br />
struct SOVert {<br />
char depth;<br />
char foam;<br />
char wet;<br />
char filler;<br />
} oceanVert;<br />
struct SMVert {<br />
unsigned __int16 s;<br />
unsigned __int16 t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} verts[9*9];<br />
struct SLTiles {<br />
char tiles[8][8];<br />
// 0x0f or 0x8 mean don't render (?, TC: 0xF)<br />
// &0xf: liquid type (1: ocean, 3: slime, 4: river, 6: magma)<br />
// 0x10:<br />
// 0x20:<br />
// 0x40: not low depth (forced swimming ?)<br />
// 0x80: fatigue (?, TC: yes)<br />
} tiles;<br />
uint32_t nFlowvs;<br />
struct SWFlowv {<br />
{{Type|CAaSphere}} sphere;<br />
{{Type|C3Vector}} dir;<br />
float velocity;<br />
float amplitude;<br />
float frequency;<br />
} flowvs[2]; // always 2 in file, independent on nFlowvs.<br />
};<br />
<br />
=== MCSE sub-chunk ===<br />
* split files: root<br />
*'''Sound emitters.'''<br />
<br />
This seems to be a bit different to that structure, ObscuR posted back then. From what I can see, WoW takes only 0x1C bytes per entry. Quite a big difference. This change might have happened, when they introduced the {{Template:DBRef|table=SoundEntriesAdvanced}}.<br />
<br />
Also see {{Template:DBRef|table=WorldChunkSounds}}.<br />
<br />
struct CWSoundEmitter<br />
{<br />
/*000h*/ {{Template:Type/foreign_key|table=SoundEntriesAdvanced}} entry_id;<br />
/*004h*/ {{Template:Type|C3Vector}} position;<br />
/*008h*/ <br />
/*00Ch*/ <br />
/*010h*/ {{Template:Type|C3Vector}} size; // I'm not really sure with this. I'm far too lazy to analyze this. Seems like <br />
noone ever needed these anyway.<br />
/*014h*/ <br />
/*018h*/ <br />
} MCSE[];<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=2}}<br />
struct CWSoundEmitter<br />
{<br />
/*000h*/ UINT32 soundPointID; <br />
/*004h*/ UINT32 soundNameID; <br />
/*008h*/ C3Vector pos;<br />
/*014h*/ float minDistance; <br />
/*018h*/ float maxDistance; <br />
/*01Ch*/ float cutoffDistance; <br />
/*020h*/ UINT16 startTime; <br />
/*022h*/ UINT16 endTime;<br />
/*024h*/ UINT16 mode;<br />
/*026h*/ UINT16 groupSilenceMin; <br />
/*028h*/ UINT16 groupSilenceMax; <br />
/*02Ah*/ UINT16 playInstancesMin;<br />
/*02Ch*/ UINT16 playInstancesMax; <br />
/*02Eh*/ BYTE loopCountMin;<br />
/*02Fh*/ BYTE loopCountMax;<br />
/*030h*/ UINT16 interSoundGapMin;<br />
/*032h*/ UINT16 interSoundGapMax;<br />
/*034h*/ <br />
} MCSE[];<br />
<br />
===MCBB (MoP+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: root, lod<br />
struct // blend batches. max 256 per MCNK<br />
{<br />
uint32_t mbmh_index;<br />
uint32_t indexCount; // MBMI<br />
uint32_t indexFirst; // in addition to mbmh.mbnv_base<br />
uint32_t vertexCount; // MBNV <br />
uint32_t vertexFirst; // in addition to mbmh.mbnv_base<br />
} MCBB[];<br />
===MCMT (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* split files: tex<br />
struct<br />
{<br />
{{Template:Type/foreign_key|type=uint8_t|table=TerrainMaterial}} material_id[4]; // per MCLY<br />
} MCMT;<br />
<br />
===MCDD (Cata?+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* split files: root?<br />
struct // at least seen in WoD<br />
{<br />
// there seems to be a high-res (?) mode which is not taken into account <br />
// in live clients (32 bytes instead of 8) (?). if inlined to MCNK is low-res.<br />
uint1_t disable[8][8]; // disable detail doodads here<br />
// uint1_t disable[16][16];<br />
} MCDD;<br />
<br />
== MFBO chunk (BC+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=2}}<br />
* split files: root<br />
*'''A bounding box for flying.'''<br />
<br />
This chunk is a "box" defining, where you can fly and where you can't. It also defines the height at which one you will fall into nowhere while your camera remains at the same position. Its actually two planes with 3*3 coordinates per plane.<br />
<br />
Therefore the structure is:<br />
struct plane{<br />
short[3][3] height;<br />
};<br />
struct<br />
{<br />
plane maximum;<br />
plane minimum;<br />
} MFBO;<br />
<br />
==MTXF chunk (WotLK+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=3}}<br />
* split files: tex<br />
Array of flags for entries in MTEX. Always same number of entries as MTEX. <br />
<br />
struct SMTextureFlags<br />
{<br />
/*0x00*/ uint32_t do_not_load_specular_or_height_texture_but_use_cubemap : 1; // probably just 'disable_all_shading'<br />
/*0x00*/ uint32_t : 3; // no non-zero values in 20490<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=5}}<br />
/*0x00*/ uint32_t texture_scale : 4;<br />
/*0x01*/ uint32_t : 24; // no non-zero values in 20490<br />
#else<br />
/*0x00*/ uint32_t : 28; // no non-zero values in 20490<br />
#endif<br />
/*0x04*/<br />
} MTXF[];<br />
<br />
In WotLK this is mostly used for layers using terrain cube maps (e.g. crystalsong's "TILESET\\Terrain Cube Maps\\TCB_CrystalSong_A.blp"). Without this flag, it would try to load the _s.blp and fail loading.<br />
<br />
==MTXP chunk (MoP?+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: tex<br />
struct SMTextureParams<br />
{<br />
/*0x00*/ SMTextureFlags flags; // same as in mtxf (or taken from there if no mtxp present)<br />
/*0x04*/ float heightScale; // default 0.0 -- the _h texture values are scaled to [0, value) to determine actual "height".<br />
// this determines if textures overlap or not (e.g. roots on top of roads). <br />
/*0x08*/ float heightOffset; // default 1.0 -- note that _h based chunks are still influenced by MCAL (blendTex below)<br />
/*0x0C*/ uint32_t padding; // no default, no non-zero values in 20490<br />
/*0x10*/<br />
} mapTeXtureParameters[];<br />
<br />
If <tt>heightScale == 0.0</tt> and <tt>heightOffset == 1.0</tt>, it will not load a <tt>_h</tt> texture.<br />
<br />
<br />
'''Example for usage of textureScale and heightScale'''<br><br />
Settings: https://i.imgur.com/vk6tb91.png<br><br />
Result: https://i.imgur.com/8T86fNJ.png<br><br />
<br />
===legion terrain shader excerpt===<br />
// - pt_heightX: alpha channel of _h texture<br />
// - pt_blend: the MCAL textures combined into one channel per layer (only 1..3)<br />
// - pt_layerX: MCAL data<br />
// - in_vertexColor: MCCV<br />
<br />
vec3 blendTex = texture(pt_blend, in_tcBlend).rgb; <br />
// then layer 0 is 1-sum to fill up<br />
vec4 layer_weights = vec4(1.0 - clamp(sum(blendTex), 0, 1), blendTex); // sum(x)=dot(vecX(1.0),x) where X = size of x<br />
// if no _h (scale = 0, offset = 1) → this degrades to layer_pct = layer_weights, so fine to use always<br />
vec4 layer_pct = vec4 ( layer_weights.x * (texture(pt_height0, tc0).a * pc_heightScale[0] + pc_heightOffset[0])<br />
, layer_weights.y * (texture(pt_height1, tc1).a * pc_heightScale[1] + pc_heightOffset[1])<br />
, layer_weights.z * (texture(pt_height2, tc2).a * pc_heightScale[2] + pc_heightOffset[2])<br />
, layer_weights.w * (texture(pt_height3, tc3).a * pc_heightScale[3] + pc_heightOffset[3])<br />
);<br />
<br />
vec4 layer_pct_max = vec4(max(layer_pct.x, layer_pct.y, layer_pct.z, layer_pct.w)); // max(a,b,c,d)=max(max(a,b),max(c,d))<br />
// 1. cut off layers contributing really little (max - pct > 1) by clamping diff to [0,1)<br />
// 2. scale back up a bit again<br />
layer_pct = layer_pct * (vec4(1.0) - clamp(layer_pct_max - layer_pct, 0, 1));<br />
// 3. make them relative to the sum to get actual percentages<br />
layer_pct = layer_pct / vec4(sum(layer_pct)); <br />
<br />
// and we have the actual weighted layers<br />
vec4 weightedLayer_0 = texture(pt_layer0, tc0) * layer_pct.x;<br />
vec4 weightedLayer_1 = texture(pt_layer1, tc1) * layer_pct.y;<br />
vec4 weightedLayer_2 = texture(pt_layer2, tc2) * layer_pct.z;<br />
vec4 weightedLayer_3 = texture(pt_layer3, tc3) * layer_pct.w;<br />
<br />
// these are used later in the shader. left in to emphasise that different layers contribute to different blends<br />
float metalBlend = weightedLayer_0.a + weightedLayer_1.a;<br />
float specBlend = weightedLayer_2.a + weightedLayer_3.a;<br />
<br />
// and combine weighted layers with vertex color and a constant factor to have the final diffuse layer<br />
vec3 matDiffuse = (weightedLayer_0.rgb + weightedLayer_1.rgb + weightedLayer_2.rgb + weightedLayer_3.rgb) * in_vertexColor.rgb * 2.0; // * 2.0 because mccv goes from 0.0 to 1.0<br />
==MTCG==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9}}<br />
struct {<br />
/*0x00*/ uint32_t _00; // data is used if one of _00 and _04 are non-0<br />
/*0x04*/ uint32_t _04;<br />
/*0x08*/ uint32_t colorGradingFdid;<br />
/*0x0C*/ uint32_t colorGradingRampFdid;<br />
/*0x10*/<br />
} color_grading_info[diffuse_texture_ids.size];<br />
<br />
==MBMH (MoP+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: root, lod<br />
* there can be multiple entries per map object (as different textures are possible)<br />
struct // blend mesh header<br />
{<br />
uint32_t mapObjectID; // (unique ID)<br />
uint32_t textureId; // of linked WMO<br />
uint32_t unknown; // always zero?<br />
uint32_t mbmi_count; // record count in MBMI for this mesh<br />
uint32_t mbnv_count; // record count in MBNV for this mesh<br />
uint32_t mbmi_start; // start record into MBMI for this mesh<br />
uint32_t mbnv_start; // start record into MBNV for this mesh<br />
} MBMH[];<br />
<br />
==MBBB (MoP+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: root, lod<br />
* each one corresponds to a MBMH entry of same index<br />
struct // blend mesh bounding boxes<br />
{<br />
uint32_t mapObjectID; // (unique ID) -- repeated for unknown reason<br />
{{Template:Type|CAaBox}} bounding;<br />
} MBBB[];<br />
<br />
==MBNV (MoP+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: root, lod<br />
struct // blend mesh vertices<br />
{<br />
{{Template:Type|C3Vector}} pos;<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texture_coordinates;<br />
{{Template:Type|CArgb}} color[3]; // used: PN: none; PNC: 0; PNC2: 0, 1; PNC2T: 0, 2<br />
} MBNV[];<br />
<br />
==MBMI (MoP+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
* split files: root, lod<br />
struct // blend mesh indices<br />
{<br />
uint16_t index;<br />
} MBMI[];<br />
==MAMP (Cata+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* split files: tex<br />
struct<br />
{<br />
char fred; // texture_size = 64 / (2^mamp_value). either defined here or in MHDR.mamp_value. <br />
} mamp;<br />
==MLHD (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
struct <br />
{<br />
uint32_t unknown;<br />
float some_kind_of_bounding[6];<br />
} ml_header;<br />
==MLVH (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
float ml_v_heightData[129*129 + 128*128 + additional]; // global height map + additional data of not fixed size<br />
<br />
The heights here are global. You dont need to add z offset opposed to MCNK.<br />
<br />
The data goes in following order: first, 129*129 heights. The order is the same as in MCNK but without interleaved row. Starting point is same as in MCNK - (max_x, maxy) of ADT. One step is (-1600/3/128). <br />
<br />
Next follows 128*128. Everything remains the same, but starting point becomes (max_x - 0.5*(1600/3/128) , maxy - 0.5*(1600/3/128))<br />
<br />
==MLVI (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
uint16_t ml_v_indices[];<br />
<br />
These indices are to be used with GL_TRIANGLES primitive type<br />
<br />
==MLLL (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
struct<br />
{<br />
float lod; lod bands: 32, 16, 8…<br />
uint32_t height_length; <br />
uint32_t height_index; //index into MLVI<br />
uint32_t mapAreaLow_length;<br />
uint32_t mapAreaLow_index; //index into MLVI<br />
} ml_ll[];<br />
<br />
Lod is reverse here. The lower lod is - the more surface is detailed <br />
<br />
height_ is general height used for surface.<br />
<br />
mapAreaLow_ is same data contained in WDL file. It's 0 for most detailed layer<br />
<br />
----<br />
Some thoughts: [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])<br />
<br />
Least detailed level is 32.0 and most detailed level is 2. The values in MLLL correspond to values found in MLND.<br />
<br />
Since MLND is quadTree, each node is twice as narrow as it's parent. So lod is reduced incremently. <br />
<br />
Math: in ADT there are 16 MCNK in one axis, each MCNK consists of 8 square chunks(check holesDetailed)<br />
<br />
lod 32 = 16*8<br />
<br />
lod 16 = 8*8 chunks<br />
<br />
lod 8 = 4*8 chunks<br />
<br />
lod 4 = 2*8 chunks<br />
<br />
lod 2 = 8 chunks<br />
<br />
Where lod 2 essentially is the same as rendering the terrain from MCNK of main ADT<br />
<br />
Both MLLL and MLND reference MLVI. Probably, MLLL defines ranges of LOD in MLVI and MLLL is used to determine LOD level of nodes in MLND by comparasion of referenced ranges in MLVI<br />
<br />
==MLND (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
Defines quad tree for lod.<br />
struct<br />
{<br />
uint32_t index; //index into MLVI<br />
uint32_t length; //number of elements in MLVI used<br />
uint32_t _2;<br />
uint32_t _3;<br />
uint16_t indices[4]; // indexes into MLND for child leaves<br />
} ml_nd[];<br />
<br />
==MLSI (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
uint16_t ml_skirtIndices[]; // into MLVH<br />
<br />
So far I saw this chunk referncing only 129*129 part of MLVH [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])<br />
<br />
==MLLD (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
struct SMLodLiquidData<br />
{<br />
enum<br />
{<br />
Flag_HasTileData = 1,<br />
_compressed_A = 2,<br />
_compressed_B = 4,<br />
};<br />
uint32_t m_flags;<br />
// … compressed (rle?) or uncompressed data, two blobs (16384 and 2048)<br />
} lodLiquidData[];<br />
==MLLN (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
MLLN, MLLI and MLLV are order-dependent. A MLLN introduces a new liquid, the following MLLI defines the indices, the next MLLV the vertices.<br />
struct MLLN<br />
{<br />
uint32_t _0;<br />
uint32_t num_indices; // MLLI<br />
uint32_t _2;<br />
uint16_t _3a;<br />
uint16_t _3b;<br />
uint32_t _4;<br />
uint32_t _5;<br />
} ml_liquid_n;<br />
==MLLV (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
{{Template:Type|C3Vector}} ml_liquid_vertices[];<br />
==MLLI (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
{{Template:Type|C3sVector}} ml_liquid_indices[]; // 3 shorts into MLLV<br />
<br />
==MLMD (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
struct { // same as MODF but without bounding box (may be out of sync), better look at both. <br />
uint32_t mwidEntry; // they seem to be sorted based on the MLMX's radius, from largest to smallest, likely for optimization, rather than straight out the same as MODF.<br />
uint32_t uniqueId;<br />
{{Template:Type|C3Vector}} position;<br />
{{Template:Type|C3Vector}} rotation;<br />
uint16_t flags;<br />
uint16_t doodadSet;<br />
uint16_t nameSet;<br />
uint16_t unk;<br />
} lod_object_defs[];<br />
==MLMX (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
struct <br />
{<br />
{{Template:Type|CAaBox}} bounding;<br />
float radius;<br />
} lod_object_extents[]; // same count as MLMD<br />
<br />
Note that the bounding box is for the '''transformed''' model, i.e. the bounding box from inside the file, rotated and scaled, and then the bounding box of ''that''.<br />
<br />
Used for seeing objects map in legion from a defined distance based on the radius.<br />
The CAaBox is defined with a max point and a min point, the points coords are servers coordinates, so you should take the object position in MODF (wmo) or MDDF (m2) and convert it to server coords from clients coords.<br />
Radius is generally approximatively around 50 (Radius is CaaBox[3] - CaaBox[0]), the visibility object depends from the view distance param too (maybe a factor like radius * viewdistance factor)<br />
Feel free to reformulate this as i'm not familiar with wiki structures --[[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]]) 17:05, 28 January 2017 (UTC)<br />
<br />
==MLDD (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
SMDoodadDef lod_doodad_defs[]; // see MDDF<br />
==MLDX (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
struct <br />
{<br />
{{Template:Type|CAaBox}} bounding;<br />
float radius;<br />
} lod_doodad_extents[]; // same count as MLDD<br />
<br />
See MLMX for explanations --[[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]]) 17:05, 28 January 2017 (UTC)<br />
<br />
==MLDL (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
uint32_t unk[]; // same count as MLDD. If the corresponding MLDD has a flag of 0x8 this has a value otherwise 0.<br />
<br />
==MLFD (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: obj1<br />
<br />
struct <br />
{<br />
uint32_t m2LodOffset[3]; //Index into [[ADT#MLDD_(Legion+)|MLDD]] per lod<br />
uint32_t m2LodLength[3]; //Number of elements used from [[ADT#MLDD_(Legion+)|MLDD]] per lod<br />
uint32_t wmoLodOffset[3]; //Index into [[ADT#MLMD_(Legion+)|MLMD]] per lod<br />
uint32_t wmoLodLength[3]; //Number of elements used from [[ADT#MLMD_(Legion+)|MLMD]] per lod<br />
} lod_levels_for_objects; <br />
<br />
==MBMB (Legion+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
* split files: lod<br />
Contains array. Length of each element of array is 20 bytes<br />
<br />
It's related to blend meshes - {{Template:Unverified|BlendMeshBatches}}<br />
<br />
==MLMB (BfA+)==<br />
{{SectionBox/VersionRange|min_build=8.2.0.30080|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
* split files: obj0, obj1<br />
char MLMB[];<br />
<br />
==MLDB (Shadowlands+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
* split files: obj1<br />
char MLDB[]; // lod doodad batches? same count as MLDD<br />
<br />
==MWDR (Shadowlands+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
* split files: obj0, obj1<br />
struct <br />
{<br />
uint32_t begin; // Index into [[#MWDS_.28Shadowlands.2B.29|MWDS]].<br />
uint32_t end; // inclusive: [7, 10] = MWDS[7] + MWDS[8] + MWDS[9] + MWDS[10]<br />
} MWDR[];<br />
<br />
==MWDS (Shadowlands+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
* split files: obj0, obj1<br />
uint16_t MWDS[]; // index into [[WMO#MODS_chunk]]<br />
<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=ADTLodImplementation&diff=34340ADTLodImplementation2021-08-18T00:42:37Z<p>Zee: Created page with "My implementation of Adt_lod, may not be 100% but seems alright. Adt_lod is basicly just an implementation of non seamless quad tree terrain. The algorithm itself isn't imp..."</p>
<hr />
<div>My implementation of Adt_lod, may not be 100% but seems alright.<br />
<br />
Adt_lod is basicly just an implementation of non seamless quad tree terrain. <br />
<br />
The algorithm itself isn't important because everything is precalculated and saved into the lod file.<br />
We just create a big vertex buffer for each chunk, and then based on the camera position we keep updating the index buffer based on the information provided in the ADT_lod file.<br />
<br />
<br />
The vertex buffer looks something like this : <br />
[129 * 129] verts that form suqares, followed by<br />
[128 * 128] verts that reside in the middle of each square, which is followed by<br />
[x] thickness vertices<br />
<br />
The thickness verts only show up in the ADT_Lod meshes and in the WDL meshes, and are used to hide the seams that appear where the different lod chunks meet.<br />
<br />
They are just a duplicate of the vertices on the borders of each chunk, moved down a bit (32 units for ADT_lod, and 196 for WDL) WDL ones are moved further down to hide bigger gaps<br />
<br />
<br />
Here's a presudocode implementation, keep in mind I'm using Y up axis here, you may need to convert it to Z up if you're doing it like blizz<br />
<br />
// Keep track of index<br />
var index = 0;<br />
// Iterate through the 129 * 129 square verts<br />
for (var x = 0; x < 129; x++)<br />
{<br />
for (var y = 0; y < 129; y++)<br />
{<br />
vertices[index] = vec3(-x * 4.1666625, MLVH[index], -y * 4.1666625);<br />
index++;<br />
}<br />
}<br />
// Iterate through the 128 * 128 center verts<br />
for (var x = 0; x < 128; x++)<br />
{<br />
for (var y = 0; y < 128; y++)<br />
{<br />
vertices[index] = vec3(-x * 4.1666625 + 2.08333125, MLVH[index], (-y - 0.5f) * 4.1666625);<br />
index++;<br />
}<br />
}<br />
// Iterate through all the skirt indices in MLSI, they indicate the vertices on the edge of each LOD<br />
for (ver i = 0; i < MLSI.Length; i++)<br />
{<br />
// Take the existing seam vertex indicated by the index, and make a copy of it below the original<br />
vertices[index] = vertices[MLSI[i]] - vec3(0, 32f, 0);<br />
index++;<br />
}<br />
<br />
<br />
Now for the indices, you navigate down the quad tree MLND, as far as you need depending on the distance from camera to your ADT chunk at the current moment<br />
My implementation goes through all 4 leaf nodes at once, but you may choose to calculate the distance to each of the 4 leaf nodes and only navigate down to the closer ones. I found that second option to be too much of a hassle.<br />
<br />
Once you reach the desired level, you copy the indices from MLVI based on the (index, length) described in the current MLND level.<br />
<br />
After that you find the appropriate lod in MLLL based on how deep you went through the tree, and copy the indices from MLVI based on (mapAreaLow_index, mapAreaLow_length) in MLLL. These describe the thickness triangles.<br />
<br />
Too much pseudocode to write here, good luck. For questions/discussion poke Zee</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=33623WMO2021-04-02T12:02:27Z<p>Zee: Fixed doodad lighting vector info</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1; // If set, the doodad can have textures projected onto it, e.g. the npc selection circle<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called). When enabled uses wmo interior lighting function, when disabled uses exterior lighting<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
// when A is != 0xff && A < 255, A is a MOLT index and that's used instead the RGB given here, taking distance and intensity into account<br />
// If A > MOLT count, then MOLT[0] is used<br />
// If A == 255, the shading direction vector is based on the center of the group and not the sun direction vector, the look-at vector from group bounds center to doodad position<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MDDI {<br />
char unk[4]; // probably float<br />
} something[nDoodads];<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
/*0x00*/ uint16_t unk0;<br />
/*0x02*/ char unk1[0xE];<br />
/*0x10*/<br />
} m_fog_extra_data[shall be same as MFOG count];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
*'''These are a new type of dynamic lights added in Shadowlands. E.g. Castle Nathria raid has 833 of them.'''<br />
*'''They're used for everything from torch fires to projecting light/shadow on the ground to make it look like light is coming through a window.'''<br />
<br />
struct MNLD {<br />
int type; // 0 = Point light (sphere), 1 = Spot light (cone) <br />
int lightIndex; // Appears to be same as index in mapobject_new_light_defs[]<br />
int enableColorGradient; // 0 = false (use only startColor), 1 = true (use start and end color gradient)<br />
int indexRelated2; // Into other struct // possibly for layering lights<br />
{{Template:Type|CImVector}} startColor; // Start Color<br />
{{Template:Type|C3Vector}} position; // Light position in WMO<br />
{{Template:Type|C3Vector}} rotation; // Euler rotation in radians, for spot light rotates the light, for point light rotates the light cookie<br />
float attenStart; // Start attenuation<br />
float attenEnd; // End attenuation<br />
float intensity; // Light intensity<br />
{{Template:Type|CImVector}} endColor; // End Color<br />
float colorBlendStart; // Gradient start distance from emitter position, for mixing start and end color<br />
float colorBlendEnd; // Gradient end distance from emitter position, for mixing start and end color<br />
char gap0[4]; // empty<br />
float flickerIntensity; // Flickering light intensity<br />
float flickerSpeed; // Flickering light speed<br />
int flickerMode; // 0 = off, 1 = sine curve, 2 = noise curve, 3 = noise step curve<br />
{{Template:Type|C3Vector}} field_54; // Only found 0's so far<br />
char gap1[4]; // empty<br />
uint lightCookieFileID; // file ID for light cookie texture. For point light it's a cube map<br />
char gap2[20]; // empty<br />
float spotlightRadius; // The overall radius of the spot light, in radians<br />
float spotlightDropoffStart; // Start of drop-off gradient, in radians. Starts at center, ends at edge. Controls the rate at which light intensity decreases from the center to the edge of the spot light beam<br />
float spotlightDropoffEnd; // End of drop-off gradient, in radians. Both start and end drop-off angles have to be smaller than radius else sharp edge<br />
uint unk0; // 14336 (power of 2)<br />
char gap4[41]; // empty<br />
char field_50; // Only found 0's so far<br />
char unk1[2]; // Only found 0's so far<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct {<br />
/*0x00*/ float _0x0; // can apparently be overwritten by groups,<br />
// potentially inside MOBA's unknown reuse blob.<br />
// used in determining locs based on moba vertices.<br />
// minimum triangle area?<br />
/*0x04*/ uint16_t layerCount;<br />
/*0x06*/ Layer detailDoodadLayers[layerCount]; // global<br />
/*0x??*/ GroupData groupData[until-end-of-chunk]; // per WMO group<br />
/*0x??*/<br />
};<br />
<br />
struct Layer {<br />
/*0x00*/ uint8_t density?; // if density? < (v14 >> 20) - 24 * (v14 / 25165824), have no<br />
// doodad on location, where v14 is a random number based on <br />
// a seed based on the vertex index<br />
<br />
/*0x01*/ uint8_t detailDoodadsCount;<br />
/*0x02*/ DetailDoodad detailDoodads[detailDoodadsCount];<br />
/*0x??*/<br />
};<br />
struct DetailDoodad {<br />
/*0x00*/ {{Type/foreign_key|table=GroundEffectDoodad}} doodad;<br />
/*0x04*/ uint8_t weight; // not required to accumulate to something specific.<br />
/*0x05*/<br />
};<br />
<br />
struct GroupData {<br />
/*0x00*/ uint16_t groupIndex;<br />
/*0x02*/ uint32_t dataSize;<br />
/*0x06*/ char data[dataSize]; // interpreted when parsing groups as per parse_group_data() below<br />
/*0x??*/<br />
};<br />
<br />
void parse_group_data() {<br />
restart_layer:<br />
uint16_t layer_index = read_uint16_t(); // index into detailDoodadLayers<br />
if (layer_index == 0xFFFF) {<br />
return;<br />
}<br />
<br />
restart_batch:<br />
uint16_t batch_index = read_uint16_t(); // batch as in MOBA<br />
if (batch_index == 0xFFFF) {<br />
goto restart_layer;<br />
}<br />
else if (batch_index & 0x8000) {<br />
// roll for locs of all loc_ranges of this batch<br />
goto restart_batch;<br />
}<br />
<br />
int locrange_index = 0; // loc as in triangles that satisfy some criteria<br />
// for all batches in batch order<br />
restart_locrange_index_part:<br />
uint8_t locrange_index_part = read_uint8_t();<br />
locrange_index += locrange_index_part & 0x7F;<br />
if (locrange_index_part == 0xFF) {<br />
goto restart_batch;<br />
}<br />
else if (locrange_index_part == 0x7F) { // RLE for an integer?!<br />
goto restart_locrange_index_part;<br />
}<br />
bool single_loc = locrange_index_part & 0x80;<br />
<br />
int loc = 0;<br />
restart_loc_part:<br />
uint8_t loc_part = read_uint8_t();<br />
loc += loc_part;<br />
if (loc_part == 0xFF) {<br />
goto restart_locrange_index_part; /// not in front of that, i.e. resetting to 0?!<br />
}<br />
else if (loc_part == 0xFE) { // RLE for an integer?!, yes different sentinel!<br />
goto restart_loc_part;<br />
}<br />
<br />
// take loc range for loc_range_index<br />
// if single_loc, roll once for locs[loc_range.begin + loc]<br />
// else, roll for locs[loc_range.begin + 0...loc]<br />
<br />
goto restart_batch;<br />
}<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled. If camera is AABB present in a group with this flag, and not present in any group with SMOGroup::INTERIOR, render all exteriors<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). If group has SMOGroup::INTERIOR flag and this flag then exterior lighting is used for the group)<br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups). Seems to disable shadow casting when on<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE. When set seems to disable rendering of batches, but still renders doodads<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
''('''M'''ap'''O'''bject '''V'''ertex '''I'''ndices)''<br />
<br />
The group's vertex indices from the group's vertex list (MOVT, MONR, MOTV) to form triangles. <br />
<br />
uint16[] Indices;<br />
<br />
Three indices form a single triangle. Therefore, the number of indices should be divisible by 3. <br />
<br />
All triangles are set in a right-handed coordinate system, which means the order of vertices is anti-clockwise to make a front-face triangle (positive area). When used in a left-handed coordinate system, the 2nd and 3rd vertex indices of each triangle have to be swapped, otherwise these triangles form a negative area, and with back-side culling enabled, get culled. When incorrectly set, a 3D renderer will make textured meshes look "inside-out".<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
==MFVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.???}}<br />
uint16_t mapobject_fog_volume_refs[]; // into MFOG (and MFED)<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=33620WMO2021-04-01T21:10:10Z<p>Zee: Some observations about rendering / culling</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1; // If set, the doodad can have textures projected onto it, e.g. the npc selection circle<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called). When enabled uses wmo interior lighting function, when disabled uses exterior lighting<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
// when A is != 0xff, A is a MOLT index and that's used instead the RGB given here, taking distance and intensity into account<br />
// The shading direction vector for interior doodads seems to be based on the center of the group and not the sun direction vector, the look-at vector from group bounds center to doodad position<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MDDI {<br />
char unk[4]; // probably float<br />
} something[nDoodads];<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
/*0x00*/ uint16_t unk0;<br />
/*0x02*/ char unk1[0xE];<br />
/*0x10*/<br />
} m_fog_extra_data[shall be same as MFOG count];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
*'''These are a new type of dynamic lights added in Shadowlands. E.g. Castle Nathria raid has 833 of them.'''<br />
*'''They're used for everything from torch fires to projecting light/shadow on the ground to make it look like light is coming through a window.'''<br />
<br />
struct MNLD {<br />
int type; // 0 = Point light (sphere), 1 = Spot light (cone) <br />
int lightIndex; // Appears to be same as index in mapobject_new_light_defs[]<br />
int enableColorGradient; // 0 = false (use only startColor), 1 = true (use start and end color gradient)<br />
int indexRelated2; // Into other struct // possibly for layering lights<br />
{{Template:Type|CImVector}} startColor; // Start Color<br />
{{Template:Type|C3Vector}} position; // Light position in WMO<br />
{{Template:Type|C3Vector}} rotation; // Euler rotation in radians, for spot light rotates the light, for point light rotates the light cookie<br />
float attenStart; // Start attenuation<br />
float attenEnd; // End attenuation<br />
float intensity; // Light intensity<br />
{{Template:Type|CImVector}} endColor; // End Color<br />
float colorBlendStart; // Gradient start distance from emitter position, for mixing start and end color<br />
float colorBlendEnd; // Gradient end distance from emitter position, for mixing start and end color<br />
char gap0[4]; // empty<br />
float flickerIntensity; // Flickering light intensity<br />
float flickerSpeed; // Flickering light speed<br />
int flickerMode; // 0 = off, 1 = sine curve, 2 = noise curve, 3 = noise step curve<br />
{{Template:Type|C3Vector}} field_54; // Only found 0's so far<br />
char gap1[4]; // empty<br />
uint lightCookieFileID; // file ID for light cookie texture. For point light it's a cube map<br />
char gap2[20]; // empty<br />
float spotlightRadius; // The overall radius of the spot light, in radians<br />
float spotlightDropoffStart; // Start of drop-off gradient, in radians. Starts at center, ends at edge. Controls the rate at which light intensity decreases from the center to the edge of the spot light beam<br />
float spotlightDropoffEnd; // End of drop-off gradient, in radians. Both start and end drop-off angles have to be smaller than radius else sharp edge<br />
uint unk0; // 14336 (power of 2)<br />
char gap4[41]; // empty<br />
char field_50; // Only found 0's so far<br />
char unk1[2]; // Only found 0's so far<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct {<br />
/*0x00*/ float _0x0; // can apparently be overwritten by groups,<br />
// potentially inside MOBA's unknown reuse blob.<br />
// used in determining locs based on moba vertices.<br />
// minimum triangle area?<br />
/*0x04*/ uint16_t layerCount;<br />
/*0x06*/ Layer detailDoodadLayers[layerCount]; // global<br />
/*0x??*/ GroupData groupData[until-end-of-chunk]; // per WMO group<br />
/*0x??*/<br />
};<br />
<br />
struct Layer {<br />
/*0x00*/ uint8_t density?; // if density? < (v14 >> 20) - 24 * (v14 / 25165824), have no<br />
// doodad on location, where v14 is a random number based on <br />
// a seed based on the vertex index<br />
<br />
/*0x01*/ uint8_t detailDoodadsCount;<br />
/*0x02*/ DetailDoodad detailDoodads[detailDoodadsCount];<br />
/*0x??*/<br />
};<br />
struct DetailDoodad {<br />
/*0x00*/ {{Type/foreign_key|table=GroundEffectDoodad}} doodad;<br />
/*0x04*/ uint8_t weight; // not required to accumulate to something specific.<br />
/*0x05*/<br />
};<br />
<br />
struct GroupData {<br />
/*0x00*/ uint16_t groupIndex;<br />
/*0x02*/ uint32_t dataSize;<br />
/*0x06*/ char data[dataSize]; // interpreted when parsing groups as per parse_group_data() below<br />
/*0x??*/<br />
};<br />
<br />
void parse_group_data() {<br />
restart_layer:<br />
uint16_t layer_index = read_uint16_t(); // index into detailDoodadLayers<br />
if (layer_index == 0xFFFF) {<br />
return;<br />
}<br />
<br />
restart_batch:<br />
uint16_t batch_index = read_uint16_t(); // batch as in MOBA<br />
if (batch_index == 0xFFFF) {<br />
goto restart_layer;<br />
}<br />
else if (batch_index & 0x8000) {<br />
// roll for locs of all loc_ranges of this batch<br />
goto restart_batch;<br />
}<br />
<br />
int locrange_index = 0; // loc as in triangles that satisfy some criteria<br />
// for all batches in batch order<br />
restart_locrange_index_part:<br />
uint8_t locrange_index_part = read_uint8_t();<br />
locrange_index += locrange_index_part & 0x7F;<br />
if (locrange_index_part == 0xFF) {<br />
goto restart_batch;<br />
}<br />
else if (locrange_index_part == 0x7F) { // RLE for an integer?!<br />
goto restart_locrange_index_part;<br />
}<br />
bool single_loc = locrange_index_part & 0x80;<br />
<br />
int loc = 0;<br />
restart_loc_part:<br />
uint8_t loc_part = read_uint8_t();<br />
loc += loc_part;<br />
if (loc_part == 0xFF) {<br />
goto restart_locrange_index_part; /// not in front of that, i.e. resetting to 0?!<br />
}<br />
else if (loc_part == 0xFE) { // RLE for an integer?!, yes different sentinel!<br />
goto restart_loc_part;<br />
}<br />
<br />
// take loc range for loc_range_index<br />
// if single_loc, roll once for locs[loc_range.begin + loc]<br />
// else, roll for locs[loc_range.begin + 0...loc]<br />
<br />
goto restart_batch;<br />
}<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled. If camera is AABB present in a group with this flag, and not present in any group with SMOGroup::INTERIOR, render all exteriors<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). If group has SMOGroup::INTERIOR flag and this flag then exterior lighting is used for the group)<br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups). Seems to disable shadow casting when on<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE. When set seems to disable rendering of batches, but still renders doodads<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
''('''M'''ap'''O'''bject '''V'''ertex '''I'''ndices)''<br />
<br />
The group's vertex indices from the group's vertex list (MOVT, MONR, MOTV) to form triangles. <br />
<br />
uint16[] Indices;<br />
<br />
Three indices form a single triangle. Therefore, the number of indices should be divisible by 3. <br />
<br />
All triangles are set in a right-handed coordinate system, which means the order of vertices is anti-clockwise to make a front-face triangle (positive area). When used in a left-handed coordinate system, the 2nd and 3rd vertex indices of each triangle have to be swapped, otherwise these triangles form a negative area, and with back-side culling enabled, get culled. When incorrectly set, a 3D renderer will make textured meshes look "inside-out".<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
==MFVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.???}}<br />
uint16_t mapobject_fog_volume_refs[]; // into MFOG (and MFED)<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO/PortalCulling&diff=33503WMO/PortalCulling2021-03-13T20:43:47Z<p>Zee: </p>
<hr />
<div>// This is my implementation for portal culling, probably different than blizzard's [[User:Zee|Zee]] ([[User talk:Zee|talk]]) <br />
<br />
Find if camera position is inside the WMO bounding box<br />
If yes, find if the camera is inside the bounding box of one or more wmo groups<br />
Go through each group that the camera is present in<br />
Render group<br />
The next part needs to run recursively so that we can see through multiple portals at once<br />
Find all the portals in the current group from MOPR<br />
Check if the camera(player) is facing the front or the back of the portal:<br />
dist = norm.x * pos.x + norm.y * pos.y + norm.z * pos.z + pDist;<br />
* norm is the plane normal that comes from MOPT<br />
* pos is the camera position (player position) relative to the wmo<br />
if the side value in MOPR is negative then<br />
if dist > 0 then return true<br />
else<br />
if dist < 0 then return true<br />
If the camera is facing the front of the portal, then we need to calculate if the portal is in the view frustrum<br />
PortalCulling()<br />
the view frustrum needs to be passed recursively to the portal culling function, starting with the screen frustrum which is usually going to be between Minimum X/Y = 0 and Maximum X/Y = 1<br />
the next frustrum that is passed is that of the current portal, which is calculated as so:<br />
convert the portal vertices into screen space, by multiplying each one with the view and projection matrices of the camera<br />
go through each vertex and check:<br />
If the Z coord of the screen point is positive: (ignore negative because it's behind the camera)<br />
If the X coord of all of the screen points are less than the minimum X value of the input frustrum or is greater than the maximum X value then the in frustrum and portal don't intersect<br />
If the X value is inbetween these values then repeat the test but with the Y values.<br />
If the in frustrum and portal do intersect then<br />
find the group in MOPR that shares the same portal id and draw it<br />
Run PortalCulling() recursion here, but this time instead of passing screen frustrum, pass the min and max X/Y of the current portal's vertices in screen space</div>Zeehttps://wowdev.wiki/index.php?title=WMO/PortalCulling&diff=33502WMO/PortalCulling2021-03-13T20:42:14Z<p>Zee: Created page with "// This is my implementation for portal culling, probably different than blizzard's Zee (talk) Find if camera position is inside the WMO boun..."</p>
<hr />
<div>// This is my implementation for portal culling, probably different than blizzard's [[User:Zee|Zee]] ([[User talk:Zee|talk]]) <br />
<br />
Find if camera position is inside the WMO bounding box<br />
If yes, find if the camera is inside the bounding box of one or more wmo groups<br />
Go through each group that the camera is present in<br />
Render group<br />
The next part needs to run recursively so that we can see through multiple portals at once<br />
Find all the portals in the current group from MOPR<br />
Check if the camera(player) is facing the front or the back of the portal:<br />
dist = norm.x * pos.x + norm.y * pos.y + norm.z * pos.z + pDist;<br />
* norm is the plane normal that comes from MOPT<br />
* pos is the camera position (player position) relative to the wmo<br />
if the side value in MOPR is negative then<br />
if dist > 0 then return true<br />
else<br />
if dist < 0 then return true<br />
If the camera is facing the front of the portal, then we need to calculate if the portal is in the view frustrum<br />
PortalCulling()<br />
the view frustrum needs to be passed recursively to the portal culling function, starting with the screen frustrum which is usually going to be between Minimum X/Y = 0 and Maximum X/Y = 1<br />
the next frustrum that is passed is that of the current portal, which is calculated as so:<br />
convert the portal vertices into screen space, by multiplying each one with the view and projection matrices of the camera<br />
go through each vertex and check:<br />
If the Z coord of the screen point is positive: (ignore negative because it's behind the camera)<br />
If the X coord of all of the screen points are less than the minimum X value of the input frustrum or is greater than the maximum X value then the in frustrum and portal don't intersect<br />
If the X value is inbetween these values then repeat the test but with the Y values.<br />
If the in frustrum and portal do intersect then<br />
find the group in MOPR that shares the same portal id and draw it<br />
Run PortalCulling() recursion here, but this time instead of passing screen frustrum, pass the min and max X/Y of the current portal's vertices in screen space</div>Zeehttps://wowdev.wiki/index.php?title=M2&diff=33019M22021-02-16T10:12:38Z<p>Zee: </p>
<hr />
<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. <br />
<br />
Models are used for doodads (decoration objects), players, monsters and really everything in the game except for [[ADT|Terrain]] and [[WMO]]s.<br />
<br />
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. <br />
Details on how to request and read them are described in the page below.<br />
<br />
==Header==<br />
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.<br />
<br />
struct <br />
{<br />
// note: Offsets are for {{Template:Sandbox/VersionRange|min_expansionlevel=3}}!<br />
/*0x000*/ uint32_t magic; // "MD20". [[M2#Legion|Legion]] uses a chunked file format starting with MD21.<br />
/*0x004*/ uint32_t [[#Versions|version]];<br />
/*0x008*/ {{Template:Type/M2Array|char}} name; // should be globally unique, used to reload by name in internal clients<br />
<br />
/*0x010*/ struct<br />
{<br />
uint32_t flag_tilt_x : 1;<br />
uint32_t flag_tilt_y : 1;<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}} // TODO: verify version<br />
uint32_t flag_use_texture_combiner_combos : 1; // add textureCombinerCombos array to end of data<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=5}}<br />
uint32_t flag_load_phys_data : 1;<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=6}}<br />
uint32_t flag_unk_0x80 : 1; // with this flag unset, demon hunter tattoos stop glowing<br />
// since {{Template:Unverified|{{Template:Sandbox/PrettyVersion|expansionlevel=4|build=4.0.1.12911}}}} {{Template:Unverified|every}} model now has this flag<br />
uint32_t flag_camera_related : 1; // TODO: verify version<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}} // TODO: verify version, these are just added based on where I first saw them -- schlumpf.<br />
uint32_t flag_new_particle_record : 1; // In CATA: new version of ParticleEmitters. By default, length of M2ParticleOld is 476. <br />
// But if 0x200 is set or if version is bigger than 271, length of M2ParticleOld is 492.<br />
uint32_t flag_unk_0x400 : 1;<br />
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<br />
uint32_t flag_unk_0x1000 : 1;<br />
uint32_t flag_unk_0x2000 : 1; // seen in various legion models<br />
uint32_t flag_unk_0x4000 : 1;<br />
uint32_t flag_unk_0x8000 : 1; // seen in UI_MainMenu_Legion<br />
uint32_t flag_unk_0x10000 : 1;<br />
uint32_t flag_unk_0x20000 : 1;<br />
uint32_t flag_unk_0x40000 : 1;<br />
uint32_t flag_unk_0x80000 : 1;<br />
uint32_t flag_unk_0x100000 : 1;<br />
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<br />
#endif<br />
#endif<br />
#endif<br />
#endif<br />
} global_flags;<br />
<br />
/*0x014*/ {{Template:Type/M2Array|M2Loop|section=Global_sequences}} global_loops; // Timestamps used in [[#Global_Sequences_2|global looping animations]].<br />
/*0x01C*/ {{Template:Type/M2Array|M2Sequence|section=Animation_sequences}} sequences; // Information about the animations in the model.<br />
/*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.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2SequenceFallback|section=Playable_Animation_Lookup}} playable_animation_lookup;<br />
#endif<br />
/*0x02C*/ {{Template:Type/M2Array|M2CompBone|section=Bones}} bones; // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones ({{Template:Sandbox/PrettyVersion|expansionlevel=3}})<br />
/*0x034*/ {{Template:Type/M2Array|uint16_t|section=Key-Bone_Lookup}} boneIndicesById; //Lookup table for key skeletal bones. (alt. name: key_bone_lookup)<br />
/*0x03C*/ {{Template:Type/M2Array|M2Vertex|section=Vertices}} vertices;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2SkinProfile|section=Views_.28LOD.29}} skin_profiles;<br />
#else<br />
/*0x044*/ uint32_t num_skin_profiles; // [[#Views_.28LOD.29|Views (LOD)]] are now in [[M2/.skin|.skins]].<br />
#endif<br />
<br />
/*0x048*/ {{Template:Type/M2Array|M2Color|section=Colors}} colors; // Color and alpha animations definitions.<br />
/*0x050*/ {{Template:Type/M2Array|M2Texture|section=Textures}} textures;<br />
/*0x058*/ {{Template:Type/M2Array|M2TextureWeight|section=Transparency}} texture_weights; // Transparency of textures.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2TextureFlipbook}} texture_flipbooks; // never seen in file, 4 uint32_t fields. (M2Track<ushort>)<br />
#endif<br />
/*0x060*/ {{Template:Type/M2Array|M2TextureTransform|section=Texture_Transforms}} texture_transforms;<br />
/*0x068*/ {{Template:Type/M2Array|uint16_t|section=Replacable_texture_lookup}} textureIndicesById; // (alt. name: replacable_texture_lookup)<br />
/*0x070*/ {{Template:Type/M2Array|M2Material|section=Render_flags}} materials; // Blending modes / render flags.<br />
/*0x078*/ {{Template:Type/M2Array|uint16_t|section=Bone_Lookup_Table}} boneCombos; // (alt. name: bone_lookup_table)<br />
/*0x080*/ {{Template:Type/M2Array|uint16_t|section=Texture_lookup_table}} textureCombos; // (alt. name: texture_lookup_table)<br />
/*0x088*/ {{Template:Type/M2Array|uint16_t|section=Texture_unit_lookup_table}} textureTransformBoneMap; // (alt. name: tex_unit_lookup_table)<br />
/*0x090*/ {{Template:Type/M2Array|uint16_t|section=Transparency_lookup_table}} textureWeightCombos; // (alt. name: transparency_lookup_table)<br />
/*0x098*/ {{Template:Type/M2Array|uint16_t|section=Texture_Transforms_lookup_table}} textureTransformCombos; // (alt. name: texture_transforms_lookup_table)<br />
<br />
/*0x0A0*/ {{Template:Type|CAaBox}} bounding_box; // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height<br />
/*0x0B8*/ float bounding_sphere_radius; // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …)<br />
/*0x0BC*/ {{Template:Type|CAaBox}} collision_box;<br />
/*0x0D4*/ float collision_sphere_radius;<br />
<br />
/*0x0D8*/ {{Template:Type/M2Array|uint16_t|section=Triangles}} collisionIndices; // (alt. name: collision_triangles)<br />
/*0x0E0*/ {{Template:Type/M2Array|C3Vector|section=Vertices_2}} collisionPositions; // (alt. name: collision_vertices)<br />
/*0x0E8*/ {{Template:Type/M2Array|C3Vector|section=Normals}} collisionFaceNormals; // (alt. name: collision_normals) <br />
/*0x0F0*/ {{Template:Type/M2Array|M2Attachment|section=Attachments}} attachments; // position of equipped weapons or effects<br />
/*0x0F8*/ {{Template:Type/M2Array|uint16_t|section=Attachment_Lookup}} attachmentIndicesById; // (alt. name: attachment_lookup_table)<br />
/*0x100*/ {{Template:Type/M2Array|M2Event|section=Events}} events; // Used for playing sounds when dying and a lot else.<br />
/*0x108*/ {{Template:Type/M2Array|M2Light|section=Lights}} lights; // Lights are mainly used in loginscreens but in wands and some doodads too.<br />
/*0x110*/ {{Template:Type/M2Array|M2Camera|section=Cameras}} cameras; // The cameras are present in most models for having a model in the character tab. <br />
/*0x118*/ {{Template:Type/M2Array|uint16_t|section=Camera_lookup_table}} cameraIndicesById; // (alt. name: camera_lookup_table)<br />
/*0x120*/ {{Template:Type/M2Array|M2Ribbon|section=Ribbon_emitters}} ribbon_emitters; // Things swirling around. See the CoT-entrance for light-trails.<br />
/*0x128*/ {{Template:Type/M2Array|M2Particle|section=Particle_emitters}} particle_emitters;<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}} // TODO: verify version<br />
if (flag_use_texture_combiner_combos)<br />
{<br />
/*0x130*/ {{Template:Type/M2Array|uint16_t|section=Blend_mode_overrides}} textureCombinerCombos; // When set, textures blending is overriden by the associated array.<br />
}<br />
#endif<br />
} header;<br />
<br />
==Types==<br />
struct M2Bounds {<br />
CAaBox extent;<br />
float radius;<br />
};<br />
template<typename T><br />
struct M2Array {<br />
uint32_t size;<br />
uint32_t offset; // pointer to T, relative to begin of m2 data block (i.e. MD21 chunk content or begin of file)<br />
};<br />
struct M2TrackBase {<br />
uint16_t trackType;<br />
uint16_t loopIndex;<br />
M2Array<M2SequenceTimes> sequenceTimes;<br />
};<br />
template<typename T> <br />
struct M2PartTrack {<br />
M2Array<fixed16> times;<br />
M2Array<T> values;<br />
};<br />
template<typename T> <br />
struct M2SplineKey {<br />
T value;<br />
T inTan;<br />
T outTan;<br />
};<br />
struct M2Range {<br />
uint32_t minimum;<br />
uint32_t maximum;<br />
};<br />
<br />
==Versions==<br />
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.<br />
<br />
{| class="wikitable"<br />
! Version !! Version (Major, Minor) !! Expansion<br />
|-<br />
| 272-274 || 1.16-1.18 || Legion, Battle for Azeroth, Shadowlands<br />
|-<br />
| 272 || 1.16 || Mists of Pandaria, Warlords of Draenor<br />
|-<br />
| 265-272 || 1.9-1.16 || Cataclysm<br />
|-<br />
| 264 || 1.8 || Wrath of the Lich King<br />
|-<br />
| 260-263 || 1.4-1.7 || The Burning Crusade<br />
|-<br />
| 256-257 || 1.0-1.1 || Classic<br />
|-<br />
| 256 || 1.0 || Pre-Release<br />
|}<br />
<br />
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.<br />
<br />
==Chunks==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}<br />
<br />
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. <br />
<br />
'''NOTE''': Unlike all other chunked formats in WoW, chunk names in M2 are '''NOT''' reversed. Example: AFID == AFID in file.<br />
<br />
===MD21===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}<br />
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''.<br />
M2Data pre_legion_style_data;<br />
<br />
===PFID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}.phys</tt>}}<br />
uint32_t phys_file_id;<br />
<br />
===SFID===<br />
{{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>}}<br />
uint32_t skinFileDataIDs[header.nViews];<br />
uint32_t lod_skinFileDataIDs[lodBands /* 2? */];<br />
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.<br />
<br />
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.<br />
<br />
===AFID===<br />
{{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>}}<br />
struct<br />
{<br />
uint16_t anim_id;<br />
uint16_t sub_anim_id;<br />
uint32_t file_id; // might be 0 for "none" (so this is probably not sparse, even if it could be)<br />
} anim_file_ids[];<br />
<br />
===BFID===<br />
{{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>}}<br />
uint32_t boneFileDataIDs[];<br />
<br />
===TXAC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=It is unknown what this replaced. {{Unverified|Exact build unknown, not the first one though}}}}<br />
struct {<br />
char unk[2]; // likely used in CM2SceneRender::SetupTextureTransforms and uploaded to the shader directly. 0 otherwise.<br />
} texture_ac[m2data.header.materials.count + m2data.header.particles.count];<br />
<br />
<br />
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<br />
<br />
===EXPT===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown, not the first one though}}}}<br />
struct {<br />
_DWORD zSource;<br />
_DWORD unk1;<br />
_DWORD unk2;<br />
} extended_particle[m2data.header.particles.count];<br />
<br />
Probably outdated chunk after introduction of EXP2 chunk. If EXP2 doesnt exist, client tries to reconstruct it with data from EXPT chunk.<br />
<br />
===EXP2===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown}}}}<br />
<br />
<source><br />
struct M2ExtendedParticle<br />
{<br />
_DWORD zSource;<br />
_DWORD unk1;<br />
_DWORD unk2;<br />
M2PartTrack<fixed16> unk3;<br />
};<br />
<br />
struct M2InitExtendedParticleArray<br />
{<br />
M2Array<M2ExtendedParticle> content;<br />
} exp2;<br />
</source><br />
<br />
<br />
The length of this M2Array is the same as length of particle_emitters<br />
<br />
===PABC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to replace {{Unverified|[[#Animation_Lookup]]}} from old file. {{Unverified|Exact build unknown}}}}<br />
M2Array<uint16t> m_replacementParentSequenceLookups; // only seen in quillboarbrute*.m2. Contains AnimationIds<br />
<br />
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.<br />
<br />
This chunk called BlacklistAnimData in client.<br />
<br />
Client doesnt seem to use found index and thus whole array is used only to check if the target animation is present.<br />
<br />
M2InitBlacklistAnimData: sequenceIds<br />
<br />
===PADC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Moves texture weights from old file to a chunk. {{Unverified|Exact build unknown}}}}<br />
Defines replacement for header.texture_weights (WHY?)<br />
<br />
<source lang="cpp"><br />
struct PADC {<br />
M2Array<M2TextureWeight> texture_weights;<br />
} <br />
</source><br />
<br />
M2InitParentAnimData: parentTextureWeights<br />
<br />
===PSBC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}<br />
<br />
Defines ParentSequenceBounds<br />
M2Array<M2Bounds> parentSequenceBounds;<br />
<br />
M2InitParentSequenceBoundsData: M2Bounds<br />
<br />
===PEDC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}<br />
M2Array<M2TrackBase> parentEventData;<br />
<br />
M2InitParentEventData: eventTracks<br />
<br />
===SKID===<br />
{{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}}}}<br />
uint32_t SKeletonfileID; // links to [[M2/.skel]]<br />
<br />
===TXID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629|note=Replaces the filename for {{#Textures}} with hardcoded type.}}<br />
Replaces in-file texture filenames. <br />
struct {<br />
uint32_t fileDataID;<br />
} textureID[]<br />
<br />
===LDV1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629}}<br />
<br />
Defines LodData<br />
<br />
<source lang="cpp"><br />
struct LodData<br />
{<br />
uint16 unk0; <br />
uint16 lodCount; //maxLod = lodCount-1; <br />
float unk2_f;<br />
uint8_t particleBoneLod[4]; //lod serves as indes into this array<br />
_DWORD unk4;<br />
};<br />
</source><br />
<br />
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<br />
<br />
unk2_f is used in formula, but it's purpose is unknown<br />
fmaxf(fminf(740.0 / unk2_f, 5.0), 0.5);<br />
<br />
LodData.particleBoneLod works this way: Each model has current lod which is [0..3]. Next:<br />
<source lang="cpp"><br />
if ( lod < 1 )<br />
result = 0;<br />
<br />
if ( LodData)<br />
result = (0x10000 << LodData->particleBoneLod[lod]);<br />
else<br />
result = (0x10000 << (lod- 1));<br />
<br />
...<br />
//For each ParticleEmitter and related M2Particle record<br />
if ( result & M2CompBone[M2Particle->old.boneIndex].flags ) {<br />
//Do not animate this emitter<br />
}<br />
</source><br />
<br />
<br />
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])<br />
<br />
===RPID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>recursion_model_filename</tt>}}<br />
<br />
struct {<br />
uint32_t fileDataID;<br />
} recursive_particle_models[particle count];<br />
<br />
===GPID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>geometry_model_filename</tt>}}<br />
<br />
struct {<br />
uint32_t fileDataID;<br />
} geometry_particle_models[particle count];<br />
<br />
===WFV1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}<br />
struct WFV1 {<br />
// unknown<br />
};<br />
<br />
WFV1 = WaterFallVersion1<br />
Tells that model has PBR-ish stuff and normal map. It uses separate render path from usual M2.<br />
<br />
First tested on waterfall in 8.2.0 (fdid 2445860). And at that moment it was WFV1. <br />
<br />
Later this same tech was used to create different models, which have no relation to waterfalls. But internally even the name "waterfall" was kept.<br />
<br />
Even stuff around areas in Shadowlands map is still called waterfall internally (fdid 3445776 for example). These models have WFV3 chunk.<br />
<br />
===WFV2===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}<br />
struct WFV2 {<br />
// unknown<br />
};<br />
<br />
===PGD1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=1|min_build=1.13.2.30172}}<br />
ParticleGeosetData<br />
<br />
struct PGD1Entry {<br />
uint16_t geoset;<br />
};<br />
M2Array<PGD1Entry> p_g_d_v1; // count is equivalent to particle count<br />
<br />
M2InitParticleGeosetData: m_emitterSelectionGroup<br />
<br />
This sets geoset for each Particle emitter. And with this value Particle Emitter start to obey same geoset rules as in M2SkinSection.<br />
<br />
===WFV3===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct WaterFallDataV3 {<br />
float bumpScale; //Passed to vertex shader<br />
float value0_x;<br />
float value0_y;<br />
float value0_z;<br />
float value1_w;<br />
float value0_w;<br />
float value1_x;<br />
float value1_y;<br />
float value2_w;<br />
float value3_y;<br />
float value3_x;<br />
CImVector basecolor; // in rgba (not bgra)<br />
uint16_t flags;<br />
uint16_t unk0;<br />
float values3_w;<br />
float values3_z;<br />
float values4_y;<br />
float unk1;<br />
float unk2;<br />
float unk3;<br />
float unk4;<br />
}<br />
<br />
"value" fields are passed directly to fragment shader.<br />
<br />
===PFDC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
[[PHYS|PHYS]] physics;<br />
char PADDING[6]; // follows right after the last chunk in [[PHYS|PHYS]], or it could be data, only seen 0's<br />
// likely it isn't 6 but "whatever is needed until the next 8 (or 16?) byte alignment". --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]])<br />
Contains inline physics information in the same structure as the <tt>.phys</tt> files.<br />
<br />
===EDGF===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct EDGF {<br />
/*0x00*/ float _0x0[2];<br />
/*0x08*/ float _0x8;<br />
/*0x0C*/ char _0xC[0xC]<br />
} edgf[];<br />
<br />
EdgeFade. This data is applied to mesh only if M2Batch.flags2 has 0x8 flag<br />
<br />
===NERF===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct NERF {<br />
C2Vector coefs;<br />
};<br />
<br />
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.<br />
<br />
(coefs.x - squaredRadius) / (coefs.x - coefs.y)<br />
<br />
And this value is used as multiplier for model instace's alpha.<br />
<br />
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 08:35, 30 April 2020 (CEST)<br />
<br />
===DETL===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34365}}<br />
<br />
=Skeleton and animation=<br />
==Global sequences==<br />
A list of timestamps that act as upper limits for global sequence ranges.<br />
struct M2Loop<br />
{<br />
uint32_t timestamp;<br />
} loops[];<br />
<br />
==Standard animation block==<br />
* {{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.<br />
* 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.<br />
*'''Many values that change with time are specified using blocks like the following.'''<br />
<br />
template<typename T><br />
struct M2Array<br />
{<br />
/*0x00*/ uint32_t number;<br />
/*0x04*/ uint32_t offset_elements;<br />
/*0x08*/<br />
};<br />
<br />
struct M2TrackBase<br />
{<br />
/*0x00*/ uint16_t interpolation_type;<br />
/*0x02*/ uint16_t global_sequence;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}}<br />
M2Array<pair<uint32_t>> interpolation_ranges; // no longer required {{Template:Sandbox/VersionRange|min_expansionlevel=3}}, as implicit by minimum and maximum timestamp per sequence.<br />
M2Array<uint32_t> timestamps;<br />
#else<br />
/*0x04*/ M2Array<M2Array<uint32_t>> timestamps;<br />
#endif<br />
/*0x0C*/<br />
};<br />
<br />
template<typename T><br />
struct M2Track : M2TrackBase<br />
{<br />
/*0x00*/ // base <br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}} <br />
M2Array<T> values;<br />
#else<br />
/*0x0C*/ M2Array<M2Array<T>> values;<br />
#endif<br />
/*0x14*/<br />
};<br />
<br />
* Thus, as example, with<br />
<br />
{{Template:Type|M2CompBone|link=M2#Bones}} b;<br />
<br />
one may get the number of animations having translation information with<br />
<br />
b.translation.timestamps.number<br />
<br />
and the number of timestamps in the first animation using<br />
<br />
b.translation.timestamps.elements[0].number<br />
<br />
and the first timestamp value of the first animation via<br />
<br />
b.translation.timestamps.elements[0].elements[0]<br />
<br />
The actual translation vector for animation 0 at timestamp 0 is at<br />
<br />
b.translation.values.elements[0].elements[0]<br />
<br />
* Some timestamps/values.elements entries may have number/elements = 0, if for that animation id no animation is given.<br />
* [[#.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.<br />
* [model file name][animation id]-[animation sub-id][[#.anim_files|.anim]]<br />
* it seems like it is possible to detect if animation data is stored in-m2 or externally via<br />
** All animations which have flags & 0x20 are stored internally. <br />
** Animations which do not have flags & 0x20 are not stored internally. <br />
** Animations which do not have flags & 0x20 AND do not have flags & 0x40 are in [[#.anim_files|.anim]] files<br />
** Animations which do not have flags & 0x20 AND DO have flags 0x40 are stored... somewhere. I have no clue.<br />
<br />
===Global Sequences===<br />
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. <br />
<br />
A global sequence is completely unrelated to animations. It just always loops. This way, the sequence is not interrupted when an animation is launched.<br />
<br />
This appears to be frequently used by models that don't have more conventional animations (login screen animations, items/weapons with animated effects, etc).<br />
<br />
-- 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.<br />
<br />
===Interpolation===<br />
* If the interpolation type is 0, then values will change instantly at the timestamp, with no interpolation whatsoever.<br />
* If the interpolation type is 1, then the block linearly interpolates between keyframe values (lerp for vectors/colours, nlerp for quaternions).<br />
* 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>.<br />
* 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>.<br />
<br />
'''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)<br />
<br />
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)<br />
<br />
==.anim files==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=3}}<br />
<br />
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.<br />
<br />
'''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>.<br />
<br />
===Legion 24500===<br />
In Legion, these files are optionally chunked now. They are chunked either<br />
* if M2 header's 0x200000 flag is set and thus the new mid expansion format change is used<br />
* if the M2 has a .skel file<br />
<br />
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.<br />
<br />
====AFM2====<br />
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.<br />
====AFSA====<br />
skeleton data for attachments<br />
====AFSB====<br />
skeleton data for bones<br />
<br />
==Animation sequences==<br />
List of animations present in the model. <br />
struct M2Sequence<br />
{<br />
uint16_t id; // Animation id in [[AnimationData.dbc]]<br />
uint16_t variationIndex; // Sub-animation id: Which number in a row of animations this one is.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
uint32_t start_timestamp;<br />
uint32_t end_timestamp;<br />
#else<br />
uint32_t duration; // The length of this animation sequence in milliseconds.<br />
#endif<br />
float movespeed; // This is the speed the character moves with in this animation.<br />
uint32_t flags; // See below.<br />
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).<br />
uint16_t _padding;<br />
M2Range replay; // May both be 0 to not repeat. Client will pick a random number of repetitions within bounds if given.<br />
#if version < ??? < 6.0.1<br />
uint32_t blendTime;<br />
#else<br />
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.<br />
uint16_t blendTimeOut; // The client blends between this sequence and the next sequence for blendTimeOut milliseconds.<br />
#endif<br />
// For both blendTimeIn and blendTimeOut, the client plays both sequences simultaneously while interpolating between their animation transforms.<br />
{{Template:Type|M2Bounds}} bounds;<br />
int16_t variationNext; // id of the following animation of this AnimationID, points to an Index or is -1 if none.<br />
uint16_t aliasNext; // id in the list of animations. Used to find actual animation if this sequence is an alias (flags & 0x40)<br />
} sequences[];<br />
<br />
--[[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 .<br />
===Flags===<br />
''One thing I saw in the source is that "-1 animationblocks" in bones wont get parsed if 0x20 is not set.''<br />
<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Flag !! width="500" | Description<br />
|-<br />
| 0x01 || Sets 0x80 when loaded. (M2Init)<br />
|-<br />
| 0x02 ||<br />
|-<br />
| 0x04 ||<br />
|-<br />
| 0x08 ||<br />
|-<br />
| 0x10 || apparently set during runtime in CM2Shared::LoadLowPrioritySequence for all entries of a loaded sequence (including aliases)<br />
|-<br />
| 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.}}<br />
|-<br />
| 0x40 || has next / is alias (To find the animation data, the client skips these by following aliasNext until an animation without 0x40 is found.)<br />
|-<br />
| 0x80 || Blended animation (if either side of a transition has 0x80, lerp between end->start states, unless end==start by comparing bone values)<br />
|-<br />
| 0x100 || sequence stored in model ?<br />
|-<br />
| 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)<br />
|-<br />
| 0x400 ||<br />
|-<br />
| 0x800 || seen in Legion 24500 models<br />
|}<br />
<br />
-- 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.<br />
<br />
=== Animation Lookup===<br />
Hash table for Animations in [[AnimationData.dbc]]. For a list of animation names, see [[M2/AnimationList]].<br />
<br />
struct<br />
{<br />
uint16_t animation_index; // Index at [[#Animation_sequences|ofsAnimations]] which represents the animation in [[AnimationData.dbc]]. -1 if none.<br />
} animation_lookups[];<br />
<br />
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:<br />
<br />
<source lang="c++"><br />
M2Sequence* find_entry (uint32_t anim_id)<br />
{<br />
size_t i (anim_id % animation_lookups.count);<br />
<br />
for (size_t stride (1); true; ++stride)<br />
{<br />
if (animation_lookups[i] == -1)<br />
{<br />
return nullptr;<br />
}<br />
if (animation_sequences[animation_lookups[i]].id == anim_id)<br />
{<br />
return &animation_sequences[i];<br />
}<br />
<br />
i = (i + stride * stride) % animation_lookups.count;<br />
// so original_i + 1, original_i + 1 + 4, original_i + 1 + 4 + 9, …<br />
}<br />
<br />
[[unreachable]];<br />
}<br />
</source><br />
<br />
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.<br />
<br />
=== Playable Animation Lookup ===<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=2|note=Partially inlined into M2Sequences}}<br />
<br />
Lookup table for Playable Animation in [[AnimationData.dbc]]<br />
<br />
'''Offset Type Description'''<br />
0x00 int16 Fallback Animation ID in [[AnimationData.dbc]]<br />
0x02 int16 Flags (0, 1, 3 seen)<br />
<br />
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.<br />
<br />
Flags are used to modify how the clip should be played:<br />
'''Value Meaning'''<br />
0 Play normal<br />
1 Play backwards?<br />
3 Freeze<br />
<br />
For instance, the "HumanMale/HumanMale.m2" model has just one "Loot" animation sequence. "LootHold" and "LootUp" are obtained with flags 3 and 1.<br />
<br />
== Bones ==<br />
struct M2CompBone // probably M2Bone {{Template:Sandbox/VersionRange|max_expansionlevel=1}}<br />
{<br />
int32_t key_bone_id; // Back-reference to [[#Key-Bone_Lookup|the key bone lookup table]]. -1 if this is no key bone.<br />
enum<br />
{<br />
ignoreParentTranslate = 0x1,<br />
ignoreParentScale = 0x2,<br />
ignoreParentRotation = 0x4,<br />
spherical_billboard = 0x8,<br />
cylindrical_billboard_lock_x = 0x10,<br />
cylindrical_billboard_lock_y = 0x20,<br />
cylindrical_billboard_lock_z = 0x40,<br />
transformed = 0x200,<br />
kinematic_bone = 0x400, // MoP+: allow [[PHYS|physics]] to influence this bone<br />
helmet_anim_scaled = 0x1000, // set blend_modificator to [[HelmetAnimScaling.dbc|helmetAnimScalingRec]].m_amount for this bone<br />
something_sequence_id = 0x2000, // <=bfa+, parent_bone+submesh_id are a sequence id instead?!<br />
};<br />
uint32_t flags; <br />
int16_t parent_bone; // Parent bone ID or -1 if there is none.<br />
uint16_t [[M2/.skin#Submeshes|submesh_id]]; // Mesh part ID OR uDistToParent?<br />
union { // only {{Template:Sandbox/VersionRange|min_expansionlevel=2}}?<br />
struct {<br />
uint16_t uDistToFurthDesc;<br />
uint16_t uZRatioOfChain;<br />
} CompressData; // {{Template:Unverified|No model has ever had this part of the union used.}}<br />
uint32_t boneNameCRC; // these are for debugging only. their bone names match those in key bone lookup.<br />
};<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=1}}<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation;<br />
#else<br />
[[#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<br />
#endif<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scale;<br />
{{Template:Type|C3Vector}} pivot; // The pivot point of that bone.<br />
} bones[];<br />
The bone indices in the vertex definitions seem to index into this data.<br />
<br />
===Billboards===<br />
The billboarding bits are used for various things:<br />
* Light halos around lamps must always face the viewer<br />
* 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.<br />
<br />
Spherical and cylindrical billboard bits are mutually exclusive. Only one of them can be used for the bone.<br />
<br />
===Bone Lookup Table===<br />
Lookup table for bones referenced from [[M2/.skin#Submeshes|M2SkinSection]]. <br />
struct <br />
{<br />
uint16_t bone;<br />
} bone_lookup_table[];<br />
<br />
===Key-Bone Lookup===<br />
Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 27 for the most models. At static models it is mostly 1.<br />
struct <br />
{<br />
uint16_t bone; // -1 if none<br />
} key_bone_lookup[];<br />
<br />
==== Key Bone Names====<br />
Quoted names are confirmed.<br />
*00 "ArmL"<br />
*01 "ArmR"<br />
*02 "ShoulderL"<br />
*03 "ShoulderR"<br />
*04 "SpineLow" / "Upper Body" in animkits, if not present use Head<br />
*05 "Waist"<br />
*06 "Head"<br />
*07 "Jaw"<br />
*08 "IndexFingerR"<br />
*09 "MiddleFingerR"<br />
*10 "PinkyFingerR"<br />
*11 "RingFingerR"<br />
*12 "ThumbR"<br />
*13 "IndexFingerL"<br />
*14 "MiddleFingerL"<br />
*15 "PinkyFingerL"<br />
*16 "RingFingerL"<br />
*17 "ThumbL"<br />
*18 "$BTH"<br />
*19 "$CSR"<br />
*20 "$CSL"<br />
*21 "_Breath"<br />
*22 "_Name"<br />
*23 "_NameMount"<br />
*24 "$CHD"<br />
*25 "$CCH"<br />
*26 "Root"<br />
*27 "Wheel1" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*28 "Wheel2" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*29 "Wheel3" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*30 "Wheel4" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*31 "Wheel5" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*32 "Wheel6" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*33 "Wheel7" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*34 "Wheel8" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
<br />
Added some time after WoD:<br />
<br />
*35 "FaceAttenuation"<br />
*36 CapeParent<br />
*37 CapeChild1<br />
*38 CapeChild2<br />
*39 CapeChild3<br />
*40 CapeChild4<br />
*43 TabardParent<br />
*44 TabardChild1<br />
*45 TabardChild2<br />
*46 unk head top<br />
*47 unk head top<br />
*48 UpperBodyParent<br />
*49 NeckParent<br />
*50 NeckChild1<br />
*51 LowerBodyParent<br />
*52 "Buckle"<br />
*53 "Chest"<br />
*54 "Main"<br />
*55 LegR<br />
*56 LegL<br />
*57 KneeR<br />
*58 KneeL<br />
*59 "FootL"<br />
*60 "FootR"<br />
*61 "ElbowR"<br />
*62 "ElbowL"<br />
*63 Unk_ElbowL_Child<br />
*64 "HandR"<br />
*65 "HandL"<br />
*66 "WeaponR"<br />
*67 "WeaponL"<br />
*68 Unk_WristL_Child<br />
*69 Unk_WristR_Child<br />
*70 KneeR_UpperRig<br />
*71 KneeL_UpperRig<br />
*72 ArmR_2<br />
*73 ArmL_2<br />
*74 ElbowR_UpperRig<br />
*75 ElbowL_UpperRig<br />
*76 ForearmR<br />
*77 ForearmL<br />
*78 WristR_UpperRig<br />
*79 WristL_UpperRig<br />
<br />
====Non-Key Bone Names====<br />
<br />
This list is confirmed bone names, although they aren't used as key bones.<br />
<br />
*"$BWA"<br />
*"$BWP"<br />
*"$BWR"<br />
*"$BWS"<br />
*"$CAH"<br />
*"$CPP"<br />
*"$CSS"<br />
*"$CST"<br />
*"$DTH"<br />
*"$ESD"<br />
*"$FD1"<br />
*"$FL0"<br />
*"$FR0"<br />
*"$FSD"<br />
*"$HIT"<br />
*"$SCD"<br />
*"$SHL"<br />
*"$SHR"<br />
*"$TRD"<br />
<br />
====Hierarchy requirement====<br />
from animkits (some 8.x+9.x build):<br />
* Full Body (-1 or 26 or 5)<br />
** Upper Body (4)<br />
*** Right Shoulder (2)<br />
**** Right Arm (1)<br />
***** Right Hand (8 or 9 or 10 or 11 or 12)<br />
*** Left Shoulder (3)<br />
**** Left Arm (0)<br />
***** Left Hand (13 or 14 or 15 or 16 or 17)<br />
*** Head (6)<br />
**** Jaw (7)<br />
*** Face Upper (193) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}<br />
*** Face Lower (228) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}<br />
*** Hair (191) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}<br />
*** Beard (192) {{Sandbox/VersionRange|min_expansionlevel=9|min_build=9.0.1.34081}}<br />
** Wheel1…8 (27…34)<br />
<br />
=Geometry and rendering=<br />
==Vertices==<br />
struct M2Vertex<br />
{<br />
{{Template:Type|C3Vector}} pos;<br />
uint8 bone_weights[4];<br />
uint8 bone_indices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} tex_coords[2]; // two textures, depending on shader used<br />
};<br />
<br />
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).<br />
<br />
-- 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.<br />
<br />
==Views (LOD)==<br />
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.<br />
<br />
{{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.<br />
<br />
==Render flags and blending modes==<br />
struct M2Material<br />
{<br />
uint16_t flags;<br />
uint16_t blending_mode; // apparently a bitfield<br />
} materials[];<br />
*'''Flags:'''<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Flag !! width="1000" | Meaning<br />
|-<br />
| 0x01 || Unlit<br />
|-<br />
| 0x02 || Unfogged<br />
|-<br />
| 0x04 || Two-sided (no backface culling if set)<br />
|-<br />
| 0x08 || depthTest<br />
|-<br />
| 0x10 || depthWrite<br />
|-<br />
| 0x40 || shadow batch related ??? (seen in WoD)<br />
|-<br />
| 0x80 || shadow batch related ??? (seen in WoD)<br />
|-<br />
| 0x400 || ??? (seen in WoD)<br />
|-<br />
| 0x800 || prevent alpha for custom elements. if set, use (fully) opaque or transparent. (litSphere, shadowMonk) (MoP+)<br />
|}<br />
<br />
*'''Blending mode'''<br />
See the table at [[M2/Rendering#M2BLEND]] for a list of the current blend modes in the client.<br />
<br />
===Blend mode overrides===<br />
If this block is present (globalflags&8) and the "shading" flags of a textureunit wont be &0x8000, blending modes wont get mapped to the values above but to the ones in this block.<br />
<br />
Instead of Mapping[renderflags->blendingmode] it will be UnknownBlock[textureunit->Shading].<br />
<br />
As shading is not &0x8000 and (in their code) needs to be above 0, this may only touch Diffuse_T1.<br />
<br />
According to wod, if the M2 Header has flag 0x08, instead of reading blend mode from M2 [[#Render_flags|RenderFlags]], blendMode is read from the raw blend maps referenced in header.<br />
<br />
var flags = renderFlags[texUnit.renderFlags];<br />
var blendMode = flags >> 16;<br />
if ((header.GlobalModelFlags & 0x08) != 0 && texUnit.shader_id < mBlendMap.Length)<br />
blendMode = mBlendMap[texUnit.shader_id];<br />
<br />
==Texture unit lookup table==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=4|max_exclusive=1|note=Still present but unused in Cataclysm}}<br />
struct <br />
{<br />
uint16_t unit; // -1, 0, or 1. see below<br />
} tex_unit_lookup_table[];<br />
<br />
For models that use multitexturing, this maps given texture unit numbers into actual texture unit numbers (0 or 1).<br />
<br />
Values of -1 seem to mean environment mapping.<br />
<br />
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.<br />
<br />
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?)<br />
<br />
As a side note, on my (dated) system WoW does every texture unit in a single pass.<br />
<br />
==Colors and transparency==<br />
===Colors===<br />
struct M2Color<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> color; // vertex colors in rgb order<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alpha; // 0 - transparent, 0x7FFF - opaque. Normaly NonInterp<br />
} colors[];<br />
<br />
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.<br />
<br />
===Transparency===<br />
struct M2TextureWeight<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> weight;<br />
} textureWeights[];<br />
<br />
Specifies global transparency values in addition to the values given in the Color block. I assume these are multiplied together eventually.<br />
====Transparency lookup table====<br />
struct <br />
{<br />
uint16_t transparency;<br />
} transparency_lookup[];<br />
Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.<br />
<br />
==Textures==<br />
*'''Textures are defined globally in a list''', additionally, a lookup table is given, referenced during rendering, to select textures.<br />
struct M2Texture<br />
{<br />
uint32_t type; // see below<br />
uint32_t flags; // see below<br />
M2Array<char> filename; // for non-hardcoded textures (type != 0), this still points to a zero-byte-only string.<br />
} textures[];<br />
<br />
Note that at least {{Template:Sandbox/PrettyVersion|expansionlevel=6|build=6.0.1.18179}} relies on both<br />
<br />
* non-type-0 textures having a one-byte sized string that has a first byte of <tt>\0</tt><br />
* 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.<br />
<br />
Thus, <tt>filename</tt>'s size '''shall contain''' the zero byte, i.e. <tt>buffer size</tt> rather than <tt>string length</tt>.<br />
<br />
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.<br />
<br />
====Texture Types====<br />
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:<br />
*DBFilesClient\[[CharSections.dbc]]<br />
*DBFilesClient\[[CreatureDisplayInfo.dbc]]<br />
*DBFilesClient\[[ItemDisplayInfo.dbc]]<br />
*(possibly more)<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="800" | Meaning<br />
|-<br />
| 0 || - NONE - -- Texture given in filename<br />
|-<br />
| 1 || TEX_COMPONENT_SKIN -- Skin -- Body + clothes<br />
|-<br />
| 2 || TEX_COMPONENT_OBJECT_SKIN -- Object Skin -- Item, Capes ("Item\ObjectComponents\Cape\*.blp")<br />
|-<br />
| 3 || TEX_COMPONENT_WEAPON_BLADE -- Weapon Blade -- Used on several models but not used in the client as far as I see. Armor Reflect?<br />
|-<br />
| 4 || TEX_COMPONENT_WEAPON_HANDLE -- Weapon Handle<br />
|-<br />
| 5 || TEX_COMPONENT_ENVIRONMENT -- (OBSOLETE) Environment (Please remove from source art)<br />
|-<br />
| 6 || TEX_COMPONENT_CHAR_HAIR -- Character Hair<br />
|-<br />
| 7 || TEX_COMPONENT_CHAR_FACIAL_HAIR -- (OBSOLETE) Character Facial Hair (Please remove from source art)<br />
|-<br />
| 8 || TEX_COMPONENT_SKIN_EXTRA -- Skin Extra<br />
|-<br />
| 9 || TEX_COMPONENT_UI_SKIN -- UI Skin -- Used on inventory art M2s (1): inventoryartgeometry.m2 and inventoryartgeometryold.m2<br />
|-<br />
| 10 || TEX_COMPONENT_TAUREN_MANE -- (OBSOLETE) Tauren Mane (Please remove from source art) -- Only used in quillboarpinata.m2. I can't even find something referencing that file. Oo Is it used?<br />
|-<br />
| 11 || TEX_COMPONENT_MONSTER_1 -- Monster Skin 1 -- Skin for creatures or gameobjects #1<br />
|-<br />
| 12 || TEX_COMPONENT_MONSTER_2 -- Monster Skin 2 -- Skin for creatures or gameobjects #2<br />
|-<br />
| 13 || TEX_COMPONENT_MONSTER_3 -- Monster Skin 3 -- Skin for creatures or gameobjects #3<br />
|-<br />
| 14 || TEX_COMPONENT_ITEM_ICON -- Item Icon -- Used on inventory art M2s (2): ui-button.m2 and forcedbackpackitem.m2 (CSimpleModel_ReplaceIconTexture("texture"))<br />
|-<br />
| 15 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Background Color<br />
|-<br />
| 16 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem Color<br />
|-<br />
| 17 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Border Color<br />
|-<br />
| 18 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem<br />
|-<br />
| 19 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Eyes<br />
|-<br />
| 20 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Accessory<br />
|-<br />
| 21 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Secondary Skin<br />
|-<br />
| 22 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Secondary Hair<br />
|-<br />
| 23 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}} Secondary ?<br />
|-<br />
| 24 || {{Template:Sandbox/VersionRange|min_expansionlevel=9}}<br />
|}<br />
<br />
====Flags====<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="200" | Meaning<br />
|-<br />
| 0x1 || Texture wrap X<br />
|-<br />
| 0x2 || Texture wrap Y<br />
|-<br />
| 0x1+0x2 || Texture wrap X+Texture wrap Y<br />
|}<br />
<br />
====Texture lookup table====<br />
struct <br />
{<br />
uint16_t texture;<br />
} texture_lookup[];<br />
<br />
====Replacable texture lookup====<br />
struct <br />
{<br />
uint16_t replacement;<br />
} texture_replacements[];<br />
<br />
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.<br />
<br />
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.<br />
<br />
=Effects=<br />
==Texture Transforms==<br />
*'''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.<br />
<br />
struct M2TextureTransform<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation; // rotation center is texture center (0.5, 0.5)<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scaling;<br />
} textureTransforms[];<br />
<br />
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 <br />
<br />
So to get the proper UV rotation it would be necessary apply rotation this way:<br />
<br />
* Translate UV anim matrix to point (0.5, 0.5)<br />
* Apply rotation mat from quaternion<br />
* UV anim matrix to point (-0.5, -0.5)<br />
<br />
====Texture Transforms lookup table ====<br />
struct <br />
{<br />
uint16_t anim_texture_id; // -1 for static<br />
} anim_texture_lookup[];<br />
<br />
== Ribbon emitters ==<br />
struct M2Ribbon<br />
{<br />
uint32_t ribbonId; // Always (as I have seen): -1.<br />
uint32_t boneIndex; // A bone to attach to.<br />
{{Template:Type|C3Vector}} position; // And a position, relative to that bone.<br />
M2Array<uint16_t> textureIndices; // into [[#Textures|textures]]<br />
M2Array<uint16_t> materialIndices; // into [[#Render_flags|materials]]<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> colorTrack; // An RGB multiple for the material.[[#References|[1]]]<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alphaTrack; // And an alpha value in a short, where: 0 - transparent, 0x7FFF - opaque.<br />
[[#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]]]<br />
[[#Standard_animation_block|M2Track]]<float> heightBelowTrack; // do not set to same!<br />
float edgesPerSecond; // this defines how smooth the ribbon is. A low value may produce a lot of edges. <br />
// Edges/Sec – The number of quads generated.[[#References|[1]]]<br />
float edgeLifetime; // the length aka Lifespan. in seconds <br />
// Time in seconds that the quads stay around after being generated.[[#References|[1]]]<br />
float gravity; // use arcsin(val) to get the emission angle in degree <br />
// Can be positive or negative. Will cause the ribbon to sink or rise in the z axis over time.[[#References|[1]]]<br />
uint16_t textureRows; // tiles in texture<br />
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]]]<br />
[[#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]]]<br />
[[#Standard_animation_block|M2Track]]<uchar> visibilityTrack;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}} // TODO: verify version<br />
int16_t priorityPlane;<br />
uint16_t padding;<br />
#endif<br />
} ribbons[];<br />
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.<br />
<br />
''Parameters from the MDL format that are probably in here somewhere: emission rate, rows, cols ...?''<br />
<br />
== Particle emitters ==<br />
'''This is partly wrong as hell!''' Do not rely on this block, at all. It might even be wrong for WotLK.<br />
===M2ParticleOld===<br />
struct M2ParticleOld {<br />
uint32 particleId; // Always (as I have seen): -1.<br />
uint32 flags; // See Below<br />
{{Template:Type|C3Vector}} Position; // The position. Relative to the following bone.<br />
uint16 bone; // The [[#Bones|bone]] its attached to.<br />
union<br />
{<br />
uint16 texture; // And the [[#Texture_definitions|textures]] that are used. <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
struct // For multi-textured particles actually three ids<br />
{<br />
uint16_t texture_0 : 5;<br />
uint16_t texture_1 : 5;<br />
uint16_t texture_2 : 5;<br />
uint16_t : 1;<br />
};<br />
#endif<br />
};<br />
M2Array<char> geometry_model_filename; // if given, this emitter spawns models<br />
M2Array<char> recursion_model_filename; // if given, this emitter is an '''alias''' for the (maximum 4) emitters of the given model<br />
<br />
#if >= 263 (late Burning Crusade)<br />
uint8 blendingType; // A blending type for the particle. See Below<br />
uint8 emitterType; // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone<br />
uint16 particleColorIndex; // This one is used for [[ParticleColor.dbc]]. See below.<br />
#else<br />
uint16 blendingType; // A blending type for the particle. See Below<br />
uint16 emitterType; // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
{{Template:Type|fixed_point}}<uint8_t, 2, 5> multiTextureParamX[2];<br />
#else<br />
uint8 particleType; // Found below.<br />
uint8 headorTail; // 0 - Head, 1 - Tail, 2 - Both<br />
// Head - The particle is a billboarded square quad;[[#References|[1]]]<br />
// Tail – A tail particle is billboarded along the axis of motion and stretches in length based on speed;[[#References|[1]]]<br />
// Both – Draws both heads and tails;[[#References|[1]]]<br />
#endif<br />
uint16 textureTileRotation; // Rotation for the texture tile. (Values: -1,0,1) -- priorityPlane<br />
uint16 textureDimensions_rows; // for tiled textures<br />
uint16 textureDimensions_columns;<br />
[[#Standard_animation_block|M2Track]]<float> emissionSpeed; // Base velocity at which particles are emitted.<br />
[[#Standard_animation_block|M2Track]]<float> speedVariation; // Random variation in particle emission speed. (range: 0 to 1)<br />
[[#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; <br />
// 0 makes the velocity straight up (+z). For sphere generators, this is the maximum elevation of the initial position; <br />
// 0 makes the initial position entirely in the x-y plane (z=0).<br />
[[#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; <br />
// 0 makes the velocity have no sideways (y-axis) component. <br />
// For sphere generators, this is the maximum azimuth angle of the initial position.<br />
[[#Standard_animation_block|M2Track]]<float> gravity; // Not necessarily a float; [[#Compressed Particle Gravity|see below]].<br />
[[#Standard_animation_block|M2Track]]<float> lifespan; // Number of seconds each particle continues to be drawn after its creation.[[#References|[1]]]<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float lifespanVary; // An individual particle's lifespan is added to by <code>lifespanVary * random(-1, 1)</code><br />
#endif<br />
[[#Standard_animation_block|M2Track]]<float> emissionRate; <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float emissionRateVary; // This adds to the base emissionRate value the same way as lifespanVary. The random value is different every update.<br />
#endif<br />
[[#Standard_animation_block|M2Track]]<float> emissionAreaLength; // For plane generators, this is the width of the plane in the x-axis.<br />
// For sphere generators, this is the minimum radius.<br />
[[#Standard_animation_block|M2Track]]<float> emissionAreaWidth; // For plane generators, this is the width of the plane in the y-axis.<br />
// For sphere generators, this is the maximum radius.<br />
[[#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><br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C3Vector}}> colorTrack; // Most likely they all have 3 timestamps for {start, middle, end}.<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|fixed16}}> alphaTrack;<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C2Vector}}> scaleTrack;<br />
{{Template:Type|C2Vector}} scaleVary; // A percentage amount to randomly vary the scale of each particle<br />
[[#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)<br />
[[#The_Fake-AnimationBlock|FBlock]]<uint16> tailCellTrack;<br />
#else<br />
float midPoint; // middleTime; Middle point in lifespan (0 to 1). <br />
// Time (parametric) Middle – The relative time of the middle key to the life.[[#References|[1]]]<br />
{{Type|CImVector}}[3] colorValues; // Color, Start/Middle/End – Three values of BGRA Multiply for the particle.[[#References|[1]]]<br />
float[3] scaleValues; // Scale, Start/Middle/End – Scale of the particle in units across.[[#References|[1]]]<br />
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]]]<br />
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]]]<br />
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]]]<br />
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]]]<br />
#endif<br />
float tailLength; // A multiplier to the calculated tail particle length.[[#References|[1]]]<br />
float twinkleSpeed; // twinkleFPS; has something to do with the spread<br />
float twinklePercent; // same mechanic as MDL twinkleOnOff but non-binary in 0.11.0<br />
{{Template:Type|CRange}} twinkleScale; // min, max<br />
float burstMultiplier; // ivelScale; requires (flags & 0x40)<br />
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 ).<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float baseSpin; // Initial rotation of the particle quad<br />
float baseSpinVary;<br />
float spin; // Rotation of the particle quad per second<br />
float spinVary;<br />
#else<br />
float spin; // 0.0 for none, 1.0 to rotate the particle 360 degrees throughout its lifetime.<br />
#endif<br />
<br />
[[#M2Box|M2Box]] tumble;<br />
{{Template:Type|C3Vector}} windVector;<br />
float windTime;<br />
<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
M2Array<C3Vector> splinePoints; // Set only for spline praticle emitter. Contains array of points for spline<br />
[[#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.<br />
} particles[];<br />
<br />
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<br />
<br />
===M2Particle (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* Cata+ has multi texture support<br />
using fp_6_9 = {{Template:Type|fixed_point}}<uint16_t, 6, 9>;<br />
struct vector_2fp_6_9 { fp_6_9 x; fp_6_9 y; };<br />
struct M2Particle<br />
{<br />
M2ParticleOld old;<br />
vector_2fp_6_9 multiTextureParam0[2];<br />
vector_2fp_6_9 multiTextureParam1[2];<br />
} particles[];<br />
<br />
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.<br />
<br />
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)<br />
<br />
===Particle Flags===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="600" | Description<br />
|-<br />
| 0x1 || Particles are affected by lighting; <br />
|-<br />
| 0x2 || <br />
|-<br />
| 0x4 || <br />
|-<br />
| 0x8 || Particles travel "up" in world space, rather than model.<br />
|-<br />
| 0x10 || Do not Trail<br />
|-<br />
| 0x20 || Unlightning<br />
|-<br />
| 0x40 || Use Burst Multiplier<br />
|-<br />
| 0x80 || Particles in Model Space (Causes animation of the particle emitter to be carried over to the particles.[[#References|[1]]])<br />
|-<br />
| 0x100 || <br />
|-<br />
| 0x200 || spawn position randomized in some way?<br />
|-<br />
| 0x400 || STYLE: Pinned Particles, their quad enlarges from their creation position to where they expand.<br />
|-<br />
| 0x800 || <br />
|-<br />
| 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]]])<br />
|-<br />
| 0x2000 || clamp to ground; call CParticleEmitter2::ProjectParticle<br />
|-<br />
| 0x4000 || <br />
|-<br />
| 0x8000 || <br />
|-<br />
| 0x10000 || ChooseRandomTexture<br />
|-<br />
| 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.<br />
|-<br />
| 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.<br />
|-<br />
| 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.<br />
|-<br />
| 0x200000 || Random FlipBookStart<br />
|-<br />
| 0x400000 || Ignores Distance (or 0x4000000?!, CMapObjDef::SetDoodadEmittersIgnoresDistance has this one)<br />
|-<br />
| 0x800000 || gravity values are compressed vectors instead of z-axis values (see Compressed Particle Gravity below)<br />
|-<br />
| 0x1000000 || bone generator = bone, not joint<br />
|-<br />
| 0x4000000 || do not throttle emission rate based on distance<br />
|-<br />
| 0x10000000 || Particle uses multi-texturing (could be one of the other WoD-specific flags), see multi-textured section.<br />
|}<br />
<br />
--[[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.<br />
<br />
===ParticleColorIndex===<br />
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.<br />
===Particle types===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="500" | Description<br />
|-<br />
| 0 || "normal" particle<br />
|-<br />
| 1 || large quad from the particle's origin to its position (used in Moonwell water effects)<br />
|-<br />
| 2 || seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)<br />
|}<br />
''ParticleType is always 0 and, maybe, now (Flags & 0x40000) != 0 means "particles from origin to position". --Igor''<br />
''Checked and verified --BlinkHawk''<br />
===Particle Blendings===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="500" | Description<br />
|-<br />
| 0 || glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST);<br />
|-<br />
| 1 || glBlendFunc(GL_SRC_COLOR, GL_ONE);<br />
|-<br />
| 2 || glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
|-<br />
| 3 || glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST);<br />
|-<br />
| 4 || glBlendFunc(GL_SRC_ALPHA, GL_ONE);<br />
|}<br />
from Modelviewer source<br />
-- Rour, some WoD particle effects are using blend mode 0x7 here.<br />
===The Fake-AnimationBlock===<br />
*Its pretty much like the real one but without the "header".<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Offset !! width="90" | Type !! width="120" | Name !! width="500" | Description<br />
|-<br />
| 0x000 || uint32 || nTimestamps || The number of timestamps.<br />
|-<br />
| 0x004 || uint32 || ofsTimestamps || And the offset to them. The timestamps are shorts! (?)<br />
|-<br />
| 0x008 || uint32 || nKeys || The same number again. This time its the number of Keys / Values.<br />
|-<br />
| 0x00C || uint32 || ofsKeys || And their offset.<br />
|}<br />
But they're unable to change between different animations, so they directly point to the data.<br />
<br />
===Compressed Particle Gravity===<br />
Key values in the gravity track are decompressed at load time from a 4-byte value to a C3Vector.<br />
<br />
<nowiki><br />
struct CompressedParticleGravity {<br />
int8_t x, y;<br />
int16_t z;<br />
};<br />
for (/* each 4-byte value in the particle gravity track */) {<br />
float *pValue;<br />
C3Vector *pDst;<br />
if (particle.flags & 0x800000) {<br />
// interpret the 4 bytes at pValue as CompressedParticleGravity:<br />
CompressedParticleGravity v = new (pValue) CompressedParticleGravity();<br />
C3Vector dir = C3Vector(v.x, v.y, 0) * (1.0f / 128.0f);<br />
float z = sqrtf(1.0f - dir.Dot(dir));<br />
float mag = v.z * 0.04238648f;<br />
if (mag < 0) {<br />
z = -z;<br />
mag = -mag;<br />
}<br />
dir.z = z;<br />
dir *= mag;<br />
*pDst = dir;<br />
} else {<br />
*pDst = C3Vector(0, 0, -(*pValue));<br />
}<br />
}</nowiki><br />
<br />
<br />
===M2Box===<br />
struct M2Box {<br />
{{Template:Type|C3Vector}} ModelRotationSpeedMin;<br />
{{Template:Type|C3Vector}} ModelRotationSpeedMax;<br />
}<br />
<br />
=Miscellaneous=<br />
==Name==<br />
char name[];<br />
<br />
Informative name used for debugging purposes. Not used in retail clients.<br />
<br />
==Bounding volumes==<br />
These blocks give a simplified bounding volume for the model. Characters and creatures have just a simple box.<br />
===Vertices===<br />
This block defines the possible points used for the model. They are referenced in the triangles block later.<br />
struct <br />
{<br />
{{Template:Type|C3Vector}} position;<br />
} bounding_vertices[];<br />
<br />
===Triangles===<br />
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. <br />
<br />
struct <br />
{<br />
uint16_t index; // three entries pointing to vertices per triangle<br />
} bounding_triangles[];<br />
<br />
The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles.<br />
<br />
===Normals===<br />
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.<br />
<br />
As each vertex has a corresponding normal vector, it should be true that nBoundingNormals = nBoundingTriangles / 3.<br />
struct <br />
{<br />
{{Template:Type|C3Vector}} normal;<br />
} bounding_normals[];<br />
<br />
== Lights ==<br />
struct M2Light<br />
{<br />
/*0x00*/ uint16_t type; // Types are listed below.<br />
/*0x02*/ int16_t bone; // -1 if not attached to a bone<br />
/*0x04*/ {{Template:Type|C3Vector}} position; // relative to bone, if given<br />
/*0x10*/ [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> ambient_color;<br />
/*0x24*/ [[#Standard_animation_block|M2Track]]<float> ambient_intensity; // defaults to 1.0<br />
/*0x38*/ [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> diffuse_color;<br />
/*0x4C*/ [[#Standard_animation_block|M2Track]]<float> diffuse_intensity; // defaults to 1.0<br />
/*0x60*/ [[#Standard_animation_block|M2Track]]<float> attenuation_start;<br />
/*0x74*/ [[#Standard_animation_block|M2Track]]<float> attenuation_end;<br />
/*0x88*/ [[#Standard_animation_block|M2Track]]<uint8_t> visibility; // enabled?<br />
/*0x9C*/<br />
} lights[];<br />
<br />
Two light types:<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="200" | Description<br />
|-<br />
| 0 || Directional<br />
|-<br />
| 1 || Point light<br />
|}<br />
<br />
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.<br />
<br />
==Cameras==<br />
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.<br />
<br />
struct M2Camera<br />
{<br />
uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=4|max_exclusive=1}}<br />
float fov; // Diagonal FOV in radians. See below for conversion.<br />
#endif<br />
float far_clip;<br />
float near_clip;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> positions; // How the camera's position moves. Should be 3*3 floats.<br />
{{Template:Type|C3Vector}} position_base;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> target_position; // How the target moves. Should be 3*3 floats.<br />
{{Template:Type|C3Vector}} target_position_base;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<float>> roll; // The camera can have some roll-effect. Its 0 to 2*Pi. <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<float>> FoV; //Diagonal FOV in radians. See below for conversion.<br />
#endif<br />
} cameras[];<br />
<br />
===Camera field of view===<br />
<br />
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:<br />
<br />
<syntaxhighlight lang="cpp"><br />
float vfov = dfov / sqrt(1.0 + pow(aspect, 2.0));<br />
</syntaxhighlight><br />
<br />
The aspect ratio used is determined by the rect being presented on (eg. the game window).<br />
<br />
Note that this formula makes an assumption that the screen being projected to is planar (ie a monitor).<br />
<br />
===Camera lookup table===<br />
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.<br />
<br />
"-1" type cameras are not referenced.<br />
<br />
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.<br />
<br />
struct<br />
{<br />
uint16_t camera;<br />
} camera_lookup[];<br />
<br />
== Attachments ==<br />
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.<br />
struct M2Attachment<br />
{<br />
uint32_t id; // Referenced in the [[#Attachment_Lookup|lookup-block]] below.<br />
uint16_t bone; // attachment base<br />
uint16_t unknown; // see BogBeast.m2 in vanilla for a model having values here<br />
{{Template:Type|C3Vector}} position; // relative to bone; Often this value is the same as bone's pivot point <br />
[[#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.<br />
} attachments[];<br />
<br />
Meaning depends on type of model. The following are for creatures/characters mainly:<br />
<br />
{| style="background:#FCFCFC; color:black"<br />
! width="50" | ID !! width="250" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
|-<br />
| 0 || Shield / MountMain / ItemVisual0<br />
| 12 || Back<br />
| 24 || Special2<br />
| 36 || Bullet (version: somewhen after alpha)<br />
| 48 || RightFoot<br />
|-<br />
| 1 || HandRight / ItemVisual1<br />
| 13 || ShoulderFlapRight<br />
| 25 || Special3<br />
| 37 || SpellHandOmni (version: somewhen after alpha)<br />
| 49 || ShieldNoGlove<br />
|-<br />
| 2 || HandLeft / ItemVisual2<br />
| 14 || ShoulderFlapLeft<br />
| 26 || SheathMainHand<br />
| 38 || SpellHandDirected (version: somewhen after alpha)<br />
| 50 || SpineLow<br />
|-<br />
| 3 || ElbowRight / ItemVisual3<br />
| 15 || ChestBloodFront<br />
| 27 || SheathOffHand<br />
| 39 || VehicleSeat1 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 51 || AlteredShoulderR<br />
|-<br />
| 4 || ElbowLeft / ItemVisual4<br />
| 16 || ChestBloodBack<br />
| 28 || SheathShield<br />
| 40 || VehicleSeat2 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 52 || AlteredShoulderL<br />
|-<br />
| 5 || ShoulderRight<br />
| 17 || Breath<br />
| 29 || PlayerNameMounted<br />
| 41 || VehicleSeat3 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 53 || BeltBuckle {{Template:Sandbox/VersionRange|min_expansionlevel=5}}<br />
|-<br />
| 6 || ShoulderLeft<br />
| 18 || PlayerName<br />
| 30 || LargeWeaponLeft<br />
| 42 || VehicleSeat4 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 54 || SheathCrossbow<br />
|-<br />
| 7 || KneeRight<br />
| 19 || Base<br />
| 31 || LargeWeaponRight<br />
| 43 || VehicleSeat5 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 55 || HeadTop {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
|-<br />
| 8 || KneeLeft<br />
| 20 || Head<br />
| 32 || HipWeaponLeft<br />
| 44 || VehicleSeat6 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 57 || Backpack? {{Template:Sandbox/VersionRange|min_expansionlevel=8}}<br />
| || <br />
|-<br />
| 9 || HipRight<br />
| 21 || SpellLeftHand<br />
| 33 || HipWeaponRight<br />
| 45 || VehicleSeat7 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 60 || Unknown {{Template:Sandbox/VersionRange|min_expansionlevel=8}}<br />
| || <br />
|-<br />
| 10 || HipLeft<br />
| 22 || SpellRightHand<br />
| 34 || Chest<br />
| 46 || VehicleSeat8 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| || <br />
|-<br />
| 11 || Helm<br />
| 23 || Special1<br />
| 35 || HandArrow<br />
| 47 || LeftFoot<br />
| || <br />
|}<br />
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).<br />
===Attachment Lookup===<br />
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.<br />
struct <br />
{<br />
uint16_t attachment;<br />
} attachment_lookup[];<br />
<br />
==Events==<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
struct M2Event<br />
{<br />
uint32_t identifier; // mostly a 3 character name prefixed with '$'.<br />
uint32_t data; // This data is passed when the event is fired. <br />
uint32_t bone; // Somewhere it has to be attached.<br />
{{Template:Type|C3Vector}} position; // Relative to that bone of course, animated. Pivot without animating.<br />
[[#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".<br />
} events[];<br />
===Possible Events===<br />
There are a lot more of them. I did not list all up to now.<br />
<br />
Bow AnimEvents include $BWA, $BWP, $BWR, $BWS (If present, all of these should be childed to the right hand).<br />
<br />
{| style="background:#FCFCFC || color:black"<br />
! width="50" | ID !! width="40" | data !! width="60" | what !! width="60" | Type !! width="200" | seen to be fired on !! width="600" | Description<br />
|-<br />
| $AH[0-3] || — || PlaySoundKit (customAttack[x]) || || || soundEffect ID is defined by CreatureSoundDataRec::m_customAttack[x]<br />
|-<br />
| $BMD || — || BowMissleDestination || RangedWeapon || ||<br />
|-<br />
| $AIM || || || Vehicles || CGUnit_C::ComputeMissileTrajectory || Position used as MissileFirePos.<br />
|-<br />
| $ALT || || anim_swap_event / DisplayTransition || Unit || || CUnitDisplayTransition_C::UpdateState(1) or CUnitDisplayTransition_C::HandleAnimSwapEvent<br />
|-<br />
| $BL[0-3] || — || FootstepAnimEventHit (left) || Unit || || Backwards<br />
|-<br />
| $BR[0-3] || — || FootstepAnimEventHit (right) || Unit || || Backwards<br />
|-<br />
| $BRT || — || PlaySoundKit (birth) || || || soundEffect ID is defined by CreatureSoundDatarec::m_birthSoundID<br />
|-<br />
| $BTH || — || Breath || Unit || All situations, where nothing happens or breathing. || Adds Special Unit Effect based on unit state (under water, in-snow, …)<br />
|-<br />
| $BWP || — || PlayRangedItemPull (Bow Pull) || Unit || LoadRifle, LoadBow || <br />
|-<br />
| $BWR || — || BowRelease || Unit || AttackRifle, AttackBow, AttackThrown || <br />
|-<br />
| $CAH || — || || Unit || Attack*, *Unarmed, ShieldBash, Special* || attack hold? CGUnit_C::HandleCombatAnimEvent<br />
|-<br />
| $CCH || — || || Unit || mostly not fired, AttackThrown || CEffect::DrawFishingString needs this on the model for getting the string attachments.<br />
|-<br />
| $CFM || — || || Unit || CGCamera::UpdateMountHeightOrOffset || CGCamera::UpdateMountHeightOrOffset: Only z is used. Non-animated. Not used if $CMA<br />
|-<br />
| $CHD || || || Unit || not fired || probably does not exist?!<br />
|-<br />
| $CMA || — || || Unit || || CGCamera::UpdateMountHeightOrOffset: Position for camera<br />
|-<br />
| $CPP || || PlayCombatActionAnimKit || || || parry, anims, depending on some state, also some callback which might do more<br />
|-<br />
| $CSD || soundEntryId || PlayEmoteSound || Unit || Emote* || <br />
|-<br />
| $CSL || — || release_missiles_on_next_update if has_pending_missiles (left) || Unit || AttackRifle, SpellCast*, ChannelCast* || "x is {L or R} (""Left/right hand"") (?)"<br />
|-<br />
| $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"") (?)"<br />
|-<br />
| $CSS || — || PlayWeaponSwooshSound || || sound played depends on CGUnit_C::GetWeaponSwingType<br />
|-<br />
| $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.<br />
|-<br />
| $CVS || || || ? || || Data: SoundEntriesAdvanced.dbc, Sound — Not present in 6.0.1.18179<br />
|-<br />
| $DSE || — || DestroyEmitter || MapObj || || <br />
|-<br />
| $DSL || soundEntryId || DoodadSoundLoop (low priority) | GO || || <br />
|-<br />
| $DSO || soundEntryId || DoodadSoundOneShot || GO || || <br />
|-<br />
| $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.<br />
|-<br />
| $EAC || — || object package state enter 3, exit 2, 4, 5 || || || <br />
|-<br />
| $EDC || — || object package state enter 5, exit 3, 4, 2 || || || <br />
|-<br />
| $EMV || — || object package state enter 4, exit 3, 2, 5 || || || <br />
|-<br />
| $ESD || — || PlayEmoteStateSound || Unit || || soundEffect ID is implicit by currently played emote<br />
|-<br />
| $EWT || — || object package state enter 2, exit 3, 4, 5 || || || <br />
|-<br />
| $FD[1-9] || — || PlayFidgetSound || || || CreatureSoundDataRec::m_soundFidget (only has 5 entries, so don’t use 6-9)<br />
|-<br />
| $FDX || — || PlayUnitSound (stand) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundStandID<br />
|-<br />
| $FL[0-3] || — || FootstepAnimEventHit (left) || || || Forward<br />
|-<br />
| $FR[0-3] || — || FootstepAnimEventHit (right) || || || Forward<br />
|-<br />
| $FSD || — || HandleFootfallAnimEvent || Unit || Walk, Run (multiple times), ... || Plays some sound. Footstep? Also seen at several emotes etc. where feet are moved. CGUnit_C::HandleFootfallAnimEvent<br />
|-<br />
| $GC[0-3] || — || GameObject_C_PlayAnimatedSound || || || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x + 6] ({Custom0, Custom1, Custom2, Custom3})<br />
|-<br />
| $GO[0-5] || — || GameObject_C_PlayAnimatedSound || || || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x] ({Stand, Open, Loop, Close, Destroy, Opened})<br />
|-<br />
| $HIT || — || PlayWoundAnimKit || Unit || Attack*, *Unarmed, ShieldBash, Special* || soundEntryId depends on SpellVisualKit<br />
|-<br />
| $KVS || || || ? || || MapLoad.cpp -- not found in 6.0.1.18179<br />
|-<br />
| $RL[0-3] || — || FootstepAnimEventHit (left) || || || Running<br />
|-<br />
| $RR[0-3] || — || FootstepAnimEventHit (right) || || || Running<br />
|-<br />
| $SCD || — || PlaySoundKit (spellCastDirectedSound) || || || soundEffect ID is defined by CreatureSoundDataRec::m_spellCastDirectedSoundID<br />
|-<br />
| $SHK || spellEffectCameraShakesID || AddShake || GO || || <br />
|-<br />
| $SHL || — || ExchangeSheathedWeapon (left) || || Sheath, HipSheath || <br />
|-<br />
| $SHR || — || ExchangeSheathedWeapon (right) || || Sheath, HipSheath || <br />
|-<br />
| $SL[0-3] || — || FootstepAnimEventHit (left) || || Stop, (JumpEnd), (Shuffle*) || Stop<br />
|-<br />
| $SMD || — || PlaySoundKit (submerged) || || || soundEffect ID is defined by CreatureSoundDatarec::m_submergedSoundID<br />
|-<br />
| $SMG || — || PlaySoundKit (submerge) || || || soundEffect ID is defined by CreatureSoundDatarec::m_submergeSoundID<br />
|-<br />
| $SND || soundEntryId || PlaySoundKit (custom) || GO || || <br />
|-<br />
| $SR[0-3] || — || FootstepAnimEventHit (right) || || Stop, (JumpEnd), (Shuffle*) || Stop<br />
|-<br />
| $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)<br />
|-<br />
| $TRD || — || HandleSpellEventSound || Unit || EmoteWork*, UseStanding* || soundEffect ID is implicit by SpellRec<br />
|-<br />
| $VG[0-8] || — || HandleBoneAnimGrabEvent || || || <br />
|-<br />
| $VT[0-8] || — || HandleBoneAnimThrowEvent || || || <br />
|-<br />
| $WGG || — || PlayUnitSound (wingGlide) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingGlideID<br />
|-<br />
| $WL[0-3] || — || FootstepAnimEventHit (left) || || || <br />
|-<br />
| $WNG || — || PlayUnitSound (wingFlap) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingFlapID<br />
|-<br />
| $WR[0-3] || — || FootstepAnimEventHit (right) || || || <br />
|-<br />
| $WTB || — || || Weapons || || Weapon Trail Bottom position, also used for Bow String<br />
|-<br />
| $WTT || — || || Weapons || || Weapon Trail Top position<br />
|-<br />
| $WWG || || || ? || || Calls some function in the Object VMT. -- Not seen in 6.0.1.18179<br />
|-<br />
| DEST || || || ? || || exploding ballista, that one has a really fucked up block. Oo<br />
|-<br />
| POIN || || || Unit || not fired || Data: ?, seen on multiple models. Basilisk for example. (6801)<br />
|-<br />
| WHEE || || || ? || || Data: 601+, Used on wheels at vehicles.<br />
|-<br />
| BOTT || || || ? || || Purpose unknown. Seen in well_vortex01.m2<br />
|-<br />
| TOP || || || ? || || Purpose unknown. Seen in well_vortex01.m2<br />
|}<br />
<br />
[[Category:Format]]<br />
<br />
<br />
=References=<br />
:1. Warcraft 3 Art Tools - http://ftp.blizzard.com/pub/war3/other/WarcraftIIIArtTools1.01.zip</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=30414WMO2020-07-12T16:34:39Z<p>Zee: /* MNLD */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1;<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called)<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MDDI {<br />
char unk[4]; // probably float<br />
} something[nDoodads];<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
/*0x00*/ uint16_t unk0;<br />
/*0x02*/ char unk1[0xE];<br />
/*0x10*/<br />
} m_fog_extra_data[shall be same as MFOG count];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
*'''These are a new type of dynamic lights added in Shadowlands. E.g. Castle Nathria raid has 833 of them.'''<br />
*'''They're used for everything from torch fires to projecting light/shadow on the ground to make it look like light is coming through a window.'''<br />
<br />
struct MNLD {<br />
int type; // 0 = Point light (sphere), 1 = Spot light (cone) <br />
int lightIndex; // Appears to be same as index in mapobject_new_light_defs[]<br />
int enableColorGradient; // 0 = false (use only startColor), 1 = true (use start and end color gradient)<br />
int indexRelated2; // Into other struct // possibly for layering lights<br />
{{Template:Type|CImVector}} startColor; // Start Color<br />
{{Template:Type|C3Vector}} position; // Light position in WMO<br />
{{Template:Type|C3Vector}} rotation; // Euler rotation in radians, for spot light rotates the light, for point light rotates the light cookie<br />
float attenStart; // Start attenuation<br />
float attenEnd; // End attenuation<br />
float intensity; // Light intensity<br />
{{Template:Type|CImVector}} endColor; // End Color<br />
float colorBlendStart; // Gradient start distance from emitter position, for mixing start and end color<br />
float colorBlendEnd; // Gradient end distance from emitter position, for mixing start and end color<br />
char gap0[4]; // empty<br />
float flickerIntensity; // Flickering light intensity<br />
float flickerSpeed; // Flickering light speed<br />
int flickerMode; // 0 = off, 1 = sine curve, 2 = noise curve, 3 = noise step curve<br />
{{Template:Type|C3Vector}} field_54; // Only found 0's so far<br />
char gap1[4]; // empty<br />
uint lightCookieFileID; // file ID for light cookie texture. For point light it's a cube map<br />
char gap2[20]; // empty<br />
float spotlightRadius; // The overall radius of the spot light, in radians<br />
float spotlightDropoffStart; // Start of drop-off gradient, in radians. Starts at center, ends at edge. Controls the rate at which light intensity decreases from the center to the edge of the spot light beam<br />
float spotlightDropoffEnd; // End of drop-off gradient, in radians. Both start and end drop-off angles have to be smaller than radius else sharp edge<br />
uint unk0; // 14336 (power of 2)<br />
char gap4[41]; // empty<br />
char field_50; // Only found 0's so far<br />
char unk1[2]; // Only found 0's so far<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct {<br />
/*0x00*/ float _0x0; // can apparently be overwritten by groups,<br />
// potentially inside MOBA's unknown reuse blob.<br />
// used in determining locs based on moba vertices.<br />
// minimum triangle area?<br />
/*0x04*/ uint16_t layerCount;<br />
/*0x06*/ Layer detailDoodadLayers[layerCount]; // global<br />
/*0x??*/ GroupData groupData[until-end-of-chunk]; // per WMO group<br />
/*0x??*/<br />
};<br />
<br />
struct Layer {<br />
/*0x00*/ uint8_t density?; // if density? < (v14 >> 20) - 24 * (v14 / 25165824), have no<br />
// doodad on location, where v14 is a random number based on <br />
// a seed based on the vertex index<br />
<br />
/*0x01*/ uint8_t detailDoodadsCount;<br />
/*0x02*/ DetailDoodad detailDoodads[detailDoodadsCount];<br />
/*0x??*/<br />
};<br />
struct DetailDoodad {<br />
/*0x00*/ {{Type/foreign_key|table=GroundEffectDoodad}} doodad;<br />
/*0x04*/ uint8_t weight; // not required to accumulate to something specific.<br />
/*0x05*/<br />
};<br />
<br />
struct GroupData {<br />
/*0x00*/ uint16_t groupIndex;<br />
/*0x02*/ uint32_t dataSize;<br />
/*0x06*/ char data[dataSize]; // interpreted when parsing groups as per parse_group_data() below<br />
/*0x??*/<br />
};<br />
<br />
void parse_group_data() {<br />
restart_layer:<br />
uint16_t layer_index = read_uint16_t(); // index into detailDoodadLayers<br />
if (layer_index == 0xFFFF) {<br />
return;<br />
}<br />
<br />
restart_batch:<br />
uint16_t batch_index = read_uint16_t(); // batch as in MOBA<br />
if (batch_index == 0xFFFF) {<br />
goto restart_layer;<br />
}<br />
else if (batch_index & 0x8000) {<br />
// roll for locs of all loc_ranges of this batch<br />
goto restart_batch;<br />
}<br />
<br />
int locrange_index = 0; // loc as in triangles that satisfy some criteria<br />
// for all batches in batch order<br />
restart_locrange_index_part:<br />
uint8_t locrange_index_part = read_uint8_t();<br />
locrange_index += locrange_index_part & 0x7F;<br />
if (locrange_index_part == 0xFF) {<br />
goto restart_batch;<br />
}<br />
else if (locrange_index_part == 0x7F) { // RLE for an integer?!<br />
goto restart_locrange_index_part;<br />
}<br />
bool single_loc = locrange_index_part & 0x80;<br />
<br />
int loc = 0;<br />
restart_loc_part:<br />
uint8_t loc_part = read_uint8_t();<br />
loc += loc_part;<br />
if (loc_part == 0xFF) {<br />
goto restart_locrange_index_part; /// not in front of that, i.e. resetting to 0?!<br />
}<br />
else if (loc_part == 0xFE) { // RLE for an integer?!, yes different sentinel!<br />
goto restart_loc_part;<br />
}<br />
<br />
// take loc range for loc_range_index<br />
// if single_loc, roll once for locs[loc_range.begin + loc]<br />
// else, roll for locs[loc_range.begin + 0...loc]<br />
<br />
goto restart_batch;<br />
}<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). <br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups)<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
*'''Vertex indices for triangles.''', count = size / sizeof(unsigned short). Three 16-bit integers per triangle, that are indices into the vertex list. The numbers specify the 3 vertices for each triangle, their order makes it possible to do backface culling.<br />
<br />
uint16_t indexList[];<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
==MFVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.???}}<br />
uint16_t mapobject_fog_volume_refs[]; // into MFOG (and MFED)<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=30297WMO2020-06-15T00:34:59Z<p>Zee: /* MDDI */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1;<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called)<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
char unk[4]; // probably float<br />
} something[nDoodads];<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
char unk[0x10];<br />
} mfed[];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MNLD {<br />
int typeRelated; // Known: 0, 1<br />
int indexRelated; // Appears to be same as index in mapobject_new_light_defs[]<br />
char gap4[4];<br />
short indexRelated2; // Into other struct<br />
char gapE[6];<br />
C3Vector unsigned14;<br />
C3Vector unsigned20;<br />
float float0;<br />
float float30;<br />
float float1;<br />
char gap34[16];<br />
C3Vector field_48;<br />
C3Vector field_54;<br />
char gap60[28];<br />
float field_7c;<br />
float field_80;<br />
float field_84;<br />
char gap88[45];<br />
char field_50;<br />
char unk2[2];<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MDDL {<br />
/*0x00*/ float _0x0; // offset?<br />
/*0x04*/ uint16_t layerCount;<br />
struct {<br />
uint8_t a;<br />
uint8_t count2;<br />
struct {<br />
uint32_t a;<br />
uint8_t b;<br />
} whats?[count2];<br />
} layers?[layerCount];<br />
struct {<br />
uint16_t a;<br />
uint32_t b;<br />
} whatwhats?[????];<br />
} detailDoodadLayers[];<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). <br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups)<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
*'''Vertex indices for triangles.''', count = size / sizeof(unsigned short). Three 16-bit integers per triangle, that are indices into the vertex list. The numbers specify the 3 vertices for each triangle, their order makes it possible to do backface culling.<br />
<br />
uint16_t indexList[];<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=30296WMO2020-06-14T23:54:13Z<p>Zee: /* MAVG */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1;<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called)<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*026h*/ char _0x26[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
char unk[0x10];<br />
} mfed[];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MNLD {<br />
int typeRelated; // Known: 0, 1<br />
int indexRelated; // Appears to be same as index in mapobject_new_light_defs[]<br />
char gap4[4];<br />
short indexRelated2; // Into other struct<br />
char gapE[6];<br />
C3Vector unsigned14;<br />
C3Vector unsigned20;<br />
float float0;<br />
float float30;<br />
float float1;<br />
char gap34[16];<br />
C3Vector field_48;<br />
C3Vector field_54;<br />
char gap60[28];<br />
float field_7c;<br />
float field_80;<br />
float field_84;<br />
char gap88[45];<br />
char field_50;<br />
char unk2[2];<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MDDL {<br />
/*0x00*/ float _0x0; // offset?<br />
/*0x04*/ uint16_t layerCount;<br />
struct {<br />
uint8_t a;<br />
uint8_t count2;<br />
struct {<br />
uint32_t a;<br />
uint8_t b;<br />
} whats?[count2];<br />
} layers?[layerCount];<br />
struct {<br />
uint16_t a;<br />
uint32_t b;<br />
} whatwhats?[????];<br />
} detailDoodadLayers[];<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). <br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups)<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
*'''Vertex indices for triangles.''', count = size / sizeof(unsigned short). Three 16-bit integers per triangle, that are indices into the vertex list. The numbers specify the 3 vertices for each triangle, their order makes it possible to do backface culling.<br />
<br />
uint16_t indexList[];<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=30295WMO2020-06-14T23:53:47Z<p>Zee: /* MAVG */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1;<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called)<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*024h*/ uint16_t doodadSetID;<br />
/*028h*/ char _0x28[10];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
char unk[0x10];<br />
} mfed[];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MNLD {<br />
int typeRelated; // Known: 0, 1<br />
int indexRelated; // Appears to be same as index in mapobject_new_light_defs[]<br />
char gap4[4];<br />
short indexRelated2; // Into other struct<br />
char gapE[6];<br />
C3Vector unsigned14;<br />
C3Vector unsigned20;<br />
float float0;<br />
float float30;<br />
float float1;<br />
char gap34[16];<br />
C3Vector field_48;<br />
C3Vector field_54;<br />
char gap60[28];<br />
float field_7c;<br />
float field_80;<br />
float field_84;<br />
char gap88[45];<br />
char field_50;<br />
char unk2[2];<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MDDL {<br />
/*0x00*/ float _0x0; // offset?<br />
/*0x04*/ uint16_t layerCount;<br />
struct {<br />
uint8_t a;<br />
uint8_t count2;<br />
struct {<br />
uint32_t a;<br />
uint8_t b;<br />
} whats?[count2];<br />
} layers?[layerCount];<br />
struct {<br />
uint16_t a;<br />
uint32_t b;<br />
} whatwhats?[????];<br />
} detailDoodadLayers[];<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). <br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups)<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
*'''Vertex indices for triangles.''', count = size / sizeof(unsigned short). Three 16-bit integers per triangle, that are indices into the vertex list. The numbers specify the 3 vertices for each triangle, their order makes it possible to do backface culling.<br />
<br />
uint16_t indexList[];<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WMO&diff=30294WMO2020-06-14T23:52:05Z<p>Zee: /* MAVG */</p>
<hr />
<div>[[WMO|WMO]] files contain world map objects. They, too, have a [[Chunk|chunked]] structure just like the [[WDT]] files.<br />
<br />
There are two types of [[WMO]] files, actually:<br />
<br />
*[[WMO#WMO_root_file|WMO root file]] - lists textures ([[BLP]] Files), doodads ([[M2]] or [[MDX]] Files), etc., and orientation for the [[WMO]] groups<br />
*[[WMO#WMO_group_file|WMO group file]] - 3d model data for one unit in the world map object <br />
<br />
The root file and the groups are stored with the following filenames:<br />
<br />
*World\wmo\path\WMOName.wmo<br />
*World\wmo\path\WMOName_NNN.wmo<br />
<br />
There is a hardcoded maximum of 512 group files per root object.<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
In the alpha, [[WMO]] files were a single file rather than being split into root and group. For that reason the root data has been wrapped in a [[#MOMO|MOMO]] chunk followed by the [[#MOGP_chunk|MOGP]] chunks.<br />
<br />
=MVER=<br />
uint32_t version; // {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592|max_exclusive=1}} 14, {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} 16, {{Template:Sandbox/VersionRange|min_expansionlevel=1}} 17<br />
<br />
There never have been any additional versions after the alpha, even though the format changed a lot. Classic Blizzard.<br />
<br />
= WMO root file =<br />
<br />
The root file lists the following:<br />
<br />
* textures ([[BLP]] File references)<br />
* materials<br />
* models ([[M2|MDX / M2]] File references)<br />
* groups<br />
* visibility information<br />
* more data<br />
<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}}<br />
In version 14, the version used in the alpha, the root WMO file has an additional container <code>MOMO</code> chunk, like the <code>MOGP</code> chunk, containing all group data.<br />
<br />
==MOMO==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14.}}<br />
<br />
Rather than all chunks being top level, they have been wrapped in <code>MOMO</code>. There has been no other additional data, rather than just everything being wrapped.<br />
<br />
== MOHD chunk ==<br />
<br />
*'''Header for the map object. 64 bytes.'''<br />
<br />
struct SMOHeader<br />
{<br />
/*000h*/ uint32_t nTextures; <br />
/*004h*/ uint32_t nGroups; <br />
/*008h*/ uint32_t nPortals; <br />
/*00Ch*/ uint32_t nLights; // {{Template:Unverified|Blizzard seems to add one to the MOLT entry count when there are MOLP chunks in the groups (and maybe for MOLS too?)}}<br />
/*010h*/ uint32_t nDoodadNames; <br />
/*014h*/ uint32_t nDoodadDefs; // *<br />
/*018h*/ uint32_t nDoodadSets; <br />
/*01Ch*/ {{Template:Type|CArgb}} ambColor; // Color settings for base (ambient) color. See the flag at /*03Ch*/. <br />
/*020h*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOID}} wmoID;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
/*0x24*/ uint8_t padding[0x1c];<br />
#else <br />
/*024h*/ {{Template:Type|CAaBox}} bounding_box; // in the alpha, this bounding box was computed upon loading<br />
/*03Ch*/ uint16_t flag_do_not_attenuate_vertices_based_on_distance_to_portal : 1;<br />
/*03Ch*/ uint16_t flag_use_unified_render_path : 1; // In 3.3.5a this flag switches between classic render path (MOHD color is baked into MOCV values, all three batch types have their own rendering logic) and unified (MOHD color is added to lighting at runtime, int. and ext. batches share the same rendering logic). See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_use_liquid_type_dbc_id : 1; // use real liquid type ID from DBCs instead of local one. See MLIQ for further reference.<br />
/*03Ch*/ uint16_t flag_do_not_fix_vertex_color_alpha: 1; // In 3.3.5.a (and probably before) it prevents CMapObjGroup::FixColorVertexAlpha function to be executed. Alternatively, for the wotlk version of it, the function can be called with MOCV.a being set to 64, whjch will produce the same effect for easier implementation. For wotlk+ rendering, it alters the behavior of the said function instead. See [[https://wowdev.wiki/WMO/Rendering]] for more details.<br />
/*03Ch*/ uint16_t flag_lod : 1; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=20740}}<br />
/*03Ch*/ uint16_t flag_default_max_lod : 1; // {{Unverified|{{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21796}}}}. Usually maxLodLevel = -1 but with this flag, numLod. Entries at this level are defaulted<br />
/*03Ch*/ uint16_t : 10; // unused as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=20994}}<br />
/*03Eh*/ uint16_t numLod; // {{Template:Sandbox/VersionRange|min_expansionlevel=7|min_build=21108}} includes base lod (→ numLod = 3 means '.wmo', 'lod0.wmo' and 'lod1.wmo')<br />
#endif<br />
} header;<br />
<br />
== MOTX chunk ==<br />
{{SectionBox/VersionRange|max_build=8.1.0.28186|max_expansionlevel=8|max_exclusive=1|note=MOTX has been replaced with file data ids in MOMT}}<br />
<br />
*'''List of textures ([[BLP]] Files) used in this map object. <del>There are nTextures entries in this chunk.</del>''' <br />
<br />
A block of <del>zero-padded, zero-terminated strings,</del> that are complete filenames with paths. There will be further material information for each texture in the next chunk. The gaps between the filenames are padded with extra zeroes, but the material chunk does have some positional information for these strings.<br />
<br />
char textureNameList[];<br />
<br />
The beginning of a string is always aligned to a 4 byte address. (0, 4, 8, C). The end of the string is Zero terminated and filled with zeros until the next alignment.<br />
Sometimes there also empty alignments for no (it seems like no) real reason.<br />
<br />
{{SectionBox/VersionRange|min_build=8.1.0.28186|min_expansionlevel=8}}<br />
<br />
Starting with 8.1, MOTX is no longer used. The texture references in MOMT are file data ids directly. As of that version, there is a fallback mode though and some files still use MOTX for sake of avoiding re-export.<br />
To check if texture references in MOMT are file data ids, simply check if MOTX exist in file<br />
<br />
== MOMT chunk ==<br />
<br />
*'''Materials used in this map object, 64 bytes per texture ([[BLP]] file).'''<br />
<br />
struct SMOMaterial<br />
{<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t version; <br />
#endif<br />
<br />
/*0x00*/ uint32_t F_UNLIT : 1; // disable lighting logic in shader (but can still use vertex colors)<br />
/*0x00*/ uint32_t F_UNFOGGED : 1; // disable fog shading (rarely used)<br />
/*0x00*/ uint32_t F_UNCULLED : 1; // two-sided<br />
/*0x00*/ uint32_t F_EXTLIGHT : 1; // darkened, the intern face of windows are flagged 0x08<br />
/*0x00*/ uint32_t F_SIDN : 1; // (bright at night, unshaded) (used on windows and lamps in Stormwind, for example) (see emissive color)<br />
/*0x00*/ uint32_t F_WINDOW : 1; // lighting related (flag checked in CMapObj::UpdateSceneMaterials)<br />
/*0x00*/ uint32_t F_CLAMP_S : 1; // tex clamp S (force this material's textures to use clamp s addressing)<br />
/*0x00*/ uint32_t F_CLAMP_T : 1; // tex clamp T (force this material's textures to use clamp t addressing)<br />
/*0x00*/ uint32_t flag_0x100 : 1;<br />
/*0x00*/ uint32_t : 23; // unused as of 7.0.1.20994<br />
<br />
#if {{Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x04*/ uint32_t shader; // Index into CMapObj::s_wmoShaderMetaData. See below (shader types).<br />
#endif<br />
<br />
/*0x08*/ uint32_t blendMode; // Blending: see [[Rendering#EGxBlend|EGxBlend]]<br />
/*0x0C*/ uint32_t texture_1; // offset into MOTX; {{Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826}} No longer references MOTX but is a filedata id directly.<br />
/*0x10*/ {{Type|CImVector}} sidnColor; // emissive color; see below (emissive color)<br />
/*0x14*/ {{Type|CImVector}} frameSidnColor; // sidn emissive color; set at runtime; gets sidn-manipulated emissive color; see below (emissive color)<br />
/*0x18*/ uint32_t texture_2; // offset into MOTX<br />
/*0x1C*/ {{Type|CArgb}} diffColor;<br />
/*0x20*/ {{Type/foreign_key|table=TerrainType}} ground_type;<br />
// according to CMapObjDef::GetGroundType <br />
<br />
#if {{Sandbox/VersionRange|max_expansionlevel=0|max_build=0.6.0.3592}}<br />
char inMemPad[8];<br />
#else <br />
<br />
/*0x24*/ uint32_t texture_3; // offset into MOTX<br />
/*0x28*/ uint32_t color_2;<br />
/*0x2C*/ uint32_t flags_2;<br />
/*0x30*/ uint32_t runTimeData[4]; // This data is explicitly nulled upon loading. Contains textures or similar stuff.<br />
/*0x40*/<br />
<br />
#endif<br />
} materialList[];<br />
<br />
texture_1, 2 and 3 are start positions for texture filenames in the [[WMO#MOTX_chunk|MOTX]] data block ; texture_1 for the first texture, texture_2 for the second (see shaders), etc. texture_1 defaults to "createcrappygreentexture.blp".<br />
<br />
{{Unverified|If a texture isn't used the its start position seems to point to a chains of 4 \0}}<br />
<br />
<br />
color_2 is diffuse color : <tt>CWorldView::GatherMapObjDefGroupLiquids(): geomFactory->SetDiffuseColor(({{Type|CImVector}}*)(smo+7));</tt><br />
<br />
The flags might used to tweak alpha testing values, I'm not sure about it, but some grates and flags in IF seem to require an alpha testing threshold of 0, at other places this is greater than 0.<br />
<br />
===Texture addressing===<br />
<br />
By default, textures used by WMO materials are assigned an addressing mode of <tt>EGxTexWrapMode::GL_REPEAT</tt> (ie wrap mode).<br />
<br />
<tt>SMOMaterial</tt> flags <tt>F_CLAMP_S</tt> and <tt>F_CLAMP_T</tt> can override this default to clamp mode for the <tt>S</tt> and <tt>T</tt> dimensions, respectively.<br />
<br />
===Emissive color===<br />
<br />
The <tt>sidnColor</tt> {{Type|CImVector}} at offset <tt>0x10</tt> is used with the SIDN (self-illuminated day night) scalar from <tt>CDayNightObject</tt> to light exterior window glows (see flag <tt>0x10</tt> above).<br />
<br />
The scalar is interpolated out of a static table in the client, based on the time of day.<br />
<br />
The color value eventually is copied into offset <tt>0x14</tt> (<tt>frameSidnColor</tt>) after being manipulated by the SIDN scalar. This manipulation occurs in <tt>CMapObj::UpdateMaterials</tt>.<br />
<br />
===Shader types (12340)===<br />
<br />
Wrath of the Lich King only uses shaders 0 to 6. See below for more info on those.<br />
<br />
===Shader types (15464)===<br />
<br />
Depending on the shader, a different amount of textures is required. If there aren't enough filenames given, it defaults to Opaque (with one filename). More filenames than required are just ignored.<br />
<br />
Data is from 15464.<br />
{| style="background:#FCFCFC; color:black"<br />
|- <br />
! value <br />
! name<br />
! textures without shader<br />
! textures with shader <br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 || Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 || Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 || Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 || Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 || Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 || EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 || TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 || TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 || TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 || DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 || || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 11 || MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 || EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 || TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 || TwoLayerDiffuseEmissive || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- Seems to be invalid. Does something with MOTA (tangents).<br />
|-style="background:#F0F8FF;"<br />
| 15 || || 1 || 2 || 2 || 2<br />
|-<br />
| 16 || Diffuse || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|}<br />
<br />
tex coord and color count decide vertex buffer format: EGxVertexBufferFormat_PNC''2''T''2''<br />
<br />
===Shader types (18179)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! #textures without shader<br />
! #textures with shader<br />
! texcoord count<br />
! color count<br />
|-<br />
| 0 - Diffuse || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || 1 || 1 || 1 || 1<br />
|-<br />
| 2 - Metal || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || 1 || 2 || 1 || 1<br />
|-<br />
| 4 - Opaque || 1 || 1 || 1 || 1<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || 1 || 2 || 1 || 1<br />
|-<br />
| 6 - TwoLayerDiffuse || 1 || 2 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 8 - TwoLayerTerrain || 1 || 2 || 1 || 2 || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 10 - waterWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || 1 || 3 || 2 || 2<br />
|-<br />
| 12 - EnvMetalEmissive || 1 || 3 || 2 || 2<br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || 1 || 2 || 2 || 2<br />
|-<br />
| 14 - submarineWindow || 1 || 1 || 1 || 1 || SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || 1 || 2 || 2 || 2<br />
|-<br />
| 16 - DiffuseTerrain || 1 || 1 || 1 || 1 || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || 1 || 3 || 2 || 2<br />
|}<br />
<br />
<br />
===Shader types (26522)===<br />
{| style="background:#FCFCFC; color:black"<br />
|-<br />
! value<br />
! vertex shader<br />
! pixel shader<br />
|-<br />
| 0 - Diffuse || MapObjDiffuse_T1 || MapObjDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 1 - Specular || MapObjSpecular_T1 || MapObjSpecular<br />
|-<br />
| 2 - Metal || MapObjSpecular_T1 || MapObjMetal<br />
|-style="background:#F0F8FF;"<br />
| 3 - Env || MapObjDiffuse_T1_Refl || MapObjEnv<br />
|-<br />
| 4 - Opaque || MapObjDiffuse_T1|| MapObjOpaque<br />
|-style="background:#F0F8FF;"<br />
| 5 - EnvMetal || MapObjDiffuse_T1_Refl || MapObjEnvMetal<br />
|-<br />
| 6 - TwoLayerDiffuse || MapObjDiffuse_Comp || MapObjTwoLayerDiffuse<br />
|-style="background:#F0F8FF;"<br />
| 7 - TwoLayerEnvMetal || MapObjDiffuse_T1 || MapObjTwoLayerEnvMetal<br />
|-<br />
| 8 - TwoLayerTerrain || MapObjDiffuse_Comp_Terrain || MapObjTwoLayerTerrain || automatically adds _s in the filename of the second texture<br />
|-style="background:#F0F8FF;"<br />
| 9 - DiffuseEmissive || MapObjDiffuse_Comp || MapObjDiffuseEmissive <br />
|-<br />
| 10 - waterWindow || FFXWaterWindow || FFXWaterWindow || It's FFX instead of normal material. SMOMaterial::SH_WATERWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 11 - MaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjMaskedEnvMetal <br />
|-<br />
| 12 - EnvMetalEmissive || MapObjDiffuse_T1_Env_T2 || MapObjEnvMetalEmissive <br />
|-style="background:#F0F8FF;"<br />
| 13 - TwoLayerDiffuseOpaque || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseOpaque <br />
|-<br />
| 14 - submarineWindow || FFXSubmarineWindow|| FFXSubmarineWindow|| It's FFX instead of normal material. SMOMaterial::SH_SUBMARINEWINDOW -- automatically generates MOTA<br />
|-style="background:#F0F8FF;"<br />
| 15 - TwoLayerDiffuseEmissive || MapObjDiffuse_Comp || MapObjTwoLayerDiffuseEmissive<br />
|-<br />
| 16 - DiffuseTerrain || MapObjDiffuse_T1 || MapObjDiffuse || SMOMaterial::SH_DIFFUSE_TERRAIN -- "Blend Material": used for blending WMO with terrain (dynamic blend batches)<br />
|-style="background:#F0F8FF;"<br />
| 17 - AdditiveMaskedEnvMetal || MapObjDiffuse_T1_Env_T2 || MapObjAdditiveMaskedEnvMetal <br />
|-<br />
| 18 - TwoLayerDiffuseMod2x|| MapObjDiffuse_CompAlpha || MapObjTwoLayerDiffuseMod2x<br />
|-style="background:#F0F8FF;"<br />
| 19 - TwoLayerDiffuseMod2xNA|| MapObjDiffuse_Comp || MapObjTwoLayerDiffuseMod2xNA<br />
|-<br />
| 20 - TwoLayerDiffuseAlpha|| MapObjDiffuse_CompAlpha|| MapObjTwoLayerDiffuseAlpha<br />
|-style="background:#F0F8FF;"<br />
| 21 - Lod || MapObjDiffuse_T1 || MapObjLod <br />
|-<br />
| 22 - Parallax || MapObjParallax || MapObjParallax || SMOMaterial::SH_PARALLAX_ICE<br />
|}<br />
<br />
=== void CMapObj::CreateMaterial (unsigned int materialId) ===<br />
<br />
void CMapObj::CreateMaterial (unsigned int materialId)<br />
{<br />
assert (m_materialCount);<br />
assert (m_materialTexturesList);<br />
assert (materialId < m_materialCount);<br />
<br />
if (++m_materialTexturesList[materialId].refcount <= 1)<br />
{<br />
SMOMaterial* material = &m_smoMaterials[materialId];<br />
<br />
const char* texNames[3];<br />
texNames[0] = &m_textureFilenamesRaw[material->firstTextureOffset];<br />
texNames[1] = &m_textureFilenamesRaw[material->secondTextureOffset];<br />
texNames[2] = &m_textureFilenamesRaw[material->thirdTextureOffset];<br />
if ( *texNames[0] )<br />
texNames[0] = "createcrappygreentexture.blp";<br />
<br />
assert (material->shader < SMOMaterial::SH_COUNT);<br />
<br />
int const textureCount<br />
( CShaderEffect::s_enableShaders<br />
? s_wmoShaderMetaData[material->shader].texturesWithShader<br />
: s_wmoShaderMetaData[material->shader].texturesWithoutShader<br />
);<br />
<br />
int textures_set (0);<br />
<br />
for (; textures_set < textureCount; ++textures_set)<br />
{<br />
if (!texNames[textures_set])<br />
{<br />
material->shader = MapObjOpaque;<br />
textures_set = 1;<br />
break;<br />
}<br />
}<br />
<br />
for (; textures_set < 3; ++textures_set)<br />
{<br />
texNames[textures_set] = nullptr;<br />
}<br />
<br />
if (material->shader == MapObjTwoLayerTerrain && texNames[1])<br />
{<br />
texNames[1] = insert_specular_suffix (texNames[1]);<br />
}<br />
<br />
int flags (std::max (m_field_2C, 12));<br />
<br />
const char* parent_name (m_field_9E8 & 1 ? m_filename : nullptr);<br />
<br />
m_materialTexturesList[materialId]->textures[0] = texNames[0] ? CMap::CreateTexture (texNames[0], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[1] = texNames[1] ? CMap::CreateTexture (texNames[1], parent_name, flags) : nullptr;<br />
m_materialTexturesList[materialId]->textures[2] = texNames[2] ? CMap::CreateTexture (texNames[2], parent_name, flags) : nullptr;<br />
}<br />
}<br />
<br />
==MOUV==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.0.24473}}<br />
<br />
Optional. If not present, values are <tt>{0, 0, 0, 0}</tt> for all materials. If present, has same count as materials, so is repeating those zeros for materials not using any transformation. Currently, only a translating animation is possible for two of the texture layers.<br />
<br />
struct <br />
{<br />
C2Vector translation_speed[2];<br />
} MapObjectUV[count(materials)];<br />
<br />
The formula from <tt>translation_speed</tt> values to <tt>TexMtx</tt> translation values is along the lines of <br />
<br />
a_i = translation_i ? 1000 / translation_i : 0<br />
b_i = a_i ? (a_i < 0 ? (1 - (time? % -a_i) / -a_i) : ((time? % a_i) / a_i)) : 0<br />
<br />
Note: Until {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24920}} (i.e. just before release), a missing <tt>break;</tt> in the engine's loader will overwrite the data for MOGN with that of MOUV if MOUV comes second. Since MOGN comes second in Blizzard-exported files it works for those without issue.<br />
<br />
== MOGN chunk ==<br />
<br />
*'''List of group names for the groups in this map object.'''<br />
<br />
char groupNameList[];<br />
<br />
A contiguous block of zero-terminated strings. The names are purely informational except for "antiportal". The names are referenced from MOGI and MOGP.<br />
<br />
There are '''not''' always nGroups entries in this chunk as it contains extra empty strings and descriptive names. There are also empty entries. The names are indeed referenced in MOGI, and both the name and a descriptive name are referenced in the group file header (2 firsts uint16 of MOGP).<br />
<br />
Looks like ASCII but is not: BWL e.g. has <tt>’</tt>, so probably UTF-8.<br />
<br />
== MOGI chunk ==<br />
<br />
*'''Group information for WMO groups, 32 bytes per group, nGroups entries.'''<br />
<br />
struct SMOGroupInfo<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t offset; // absolute address<br />
uint32_t size; // includes IffChunk header<br />
#endif<br />
/*000h*/ uint32_t flags; // see [[WMO#group_flags|information in in MOGP]], they are equivalent<br />
/*004h*/ {{Template:Type|CAaBox}} bounding_box;<br />
/*01Ch*/ int32_t nameoffset; // name in [[WMO#MOGN_chunk|MOGN]] chunk (-1 for no name)<br />
/*020h*/<br />
} groupInfoList[];<br />
<br />
Groups don't have placement or orientation information, because the coordinates for the vertices in the additional. [[WMO]] files are already correctly transformed relative to (0,0,0) which is the entire [[WMO]]'s base position in model space.<br />
<br />
The name offsets point to the position in the file relative to the MOGN header.<br />
<br />
== MOSB chunk {{Unverified|(optional)}} ==<br />
<br />
*'''Skybox.''' Contains an zero-terminated filename for a skybox. (padded to 4 byte alignment if "empty"). If the first byte is 0, the skybox flag in all MOGI entries are cleared and there is no skybox.<br />
<br />
char skyboxName[];<br />
<br />
== MOSI (optional) ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Equivalent to MOSB, but a file data id. Client supports reading both for now.<br />
<br />
uint32_t skyboxFileId;<br />
<br />
== MOPV chunk ==<br />
<br />
*'''Portal vertices, one entry is a float[3], '''usually''' 4 * 3 * float per portal''' (actual number of vertices given in portal entry)<br />
<br />
{{Template:Type|C3Vector}} portalVertexList[];<br />
<br />
Portals are polygon planes (usually quads, but they can have more complex shapes) that specify where separation points between groups in a [[WMO]] are - these are usually doors or entrances, but can be placed elsewhere. Portals are used for occlusion culling, and is a known rendering technique used in many games (among them ''Unreal Tournament 2004'' and ''Descent''. See [https://en.wikipedia.org/wiki/Portal_rendering Portal Rendering on Wikipedia] and [https://en.wikipedia.org/wiki/Antiportal Antiportal on Wikipedia] for more information.<br />
<br />
Since when "playing" WoW, you're confined to the ground, checking for passing through these portals would be enough to toggle visibility for indoors or outdoors areas, however, when randomly flying around, this is not necessarily the case.<br />
<br />
So.... What happens when you're flying around on a gryphon, and you fly into that arch-shaped portal into Ironforge? How is that portal calculated? It's all cool as long as you're inside "legal" areas, I suppose. <br />
<br />
It's fun, you can actually map out the topology of the [[WMO]] using this and the [[WMO#MOPR_chunk|MOPR]] chunk. This could be used to speed up the rendering once/if I figure out how.<br />
<br />
<br />
This image explains how portal equation in MOPT and relations in MOPR are connected: [[:File:Portal Explanation.png|Portal explanation]]. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 17:06, 23 February 2017 (CET)<br />
<br />
== MOPT chunk ==<br />
<br />
*'''Portal information. 20 bytes per portal, nPortals entries.''' There is a hardcoded maximum of 128 portals in a single WMO.<br />
<br />
struct SMOPortal<br />
{<br />
uint16_t startVertex;<br />
uint16_t count;<br />
{{Template:Type|C4Plane}} plane;<br />
} portalList[];<br />
<br />
This structure describes one portal separating two WMO groups. A single portal is usually made up of four vertices in a quad (starting at startVertex and going to startVertex + count). However, portals support more complex shapes, and can fully encompass holes such as the archway leading into Ironforge and parts of the Caverns of Time.<br />
<br />
It is likely that portals are drawn as GL_TRIANGLE_STRIP in WoW's occlusion pipeline, since some portals have a vertex count that is not evenly divisible by four. One example of this is portal #21 in CavernsOfTime.wmo from Build #5875 (WoW 1.12.1), which has 10 vertices.<br />
<br />
== MOPR chunk ==<br />
<br />
* Map Object Portal References from groups. Mostly twice the number of portals. Actual count defined by sum (MOGP.portals_used).<br />
<br />
struct SMOPortalRef'' // 04-29-2005 By ObscuR''<br />
{<br />
uint16_t portalIndex; // into MOPT<br />
uint16_t groupIndex; // the other one<br />
int16_t side; // positive or negative.<br />
uint16_t filler;<br />
} portalRefList[];<br />
<br />
== MOVV chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
<br />
*'''Visible block vertices''', 0xC byte per entry.<br />
<br />
Just a list of vertices that corresponds to the visible block list.<br />
<br />
{{Template:Type|C3Vector}} visible_block_vertices[];<br />
<br />
== MOVB chunk ==<br />
Chunk is since {{Template:Sandbox/VersionRange|min_expansionlevel=8|min_build=8.1.0.28294}} optional<br />
*'''Visible block list'''<br />
<br />
struct<br />
{<br />
uint16_t firstVertex;<br />
uint16_t count;<br />
) visible_blocks[];<br />
<br />
== MOLT chunk ==<br />
<br />
*'''Lighting information. 48 bytes per light, nLights entries'''<br />
<br />
struct SMOLight<br />
{<br />
enum LightType<br />
{<br />
OMNI_LGT = 0,<br />
SPOT_LGT = 1,<br />
DIRECT_LGT = 2,<br />
AMBIENT_LGT = 3,<br />
};<br />
/*000h*/ uint8_t type;<br />
/*001h*/ uint8_t useAtten;<br />
/*002h*/ uint8_t pad[2]; // not padding as of v16<br />
/*004h*/ {{Template:Type|CImVector}} color;<br />
/*008h*/ {{Template:Type|C3Vector}} position;<br />
/*014h*/ float intensity;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}}<br />
/*018h*/ float _unk18[4]; // {{Template:Unverified|2 C2Vector ranges}}<br />
#endif<br />
/*028h*/ float attenStart;<br />
/*02Ch*/ float attenEnd;<br />
} lightList[];<br />
<br />
First 4 uint8_t are probably flags, mostly with the values (0,1,1,1).<br />
<br />
I haven't quite figured out how WoW actually does lighting, as it seems much smoother than the regular vertex lighting in my screenshots. The light parameters might be range or attenuation information, or something else entirely. Some [[WMO]] groups reference a lot of lights at once.<br />
<br />
The WoW client (at least on my system) uses only one light, which is always directional. Attenuation is always (0, 0.7, 0.03). So I suppose for models/doodads (both are [[M2]] files anyway) it selects an appropriate light to turn on. Global light is handled similarly. Some [[WMO]] textures ([[BLP]] files) have specular maps in the alpha channel, the pixel shader renderpath uses these. Still don't know how to determine direction/color for either the outdoor light or [[WMO]] local lights... :)<br />
<br />
The entire MOLT and related chunks seem to be unused at least in 3.3.5a. Changing light colors and other settings on original WMOs leads to no effect. Removing the light leads to no effect either. I assume that MOLT rendering is disabled somewhere in the WoW.exe, as it might use the same principle as the M2 light emitters which are not properly supported up to WoD. However, when you explore the WMOs in 3D editors you can clearly see that MOCV layer is different under those lamps. So, I assume they are used for baking MOCV colors and also written to the actual file in case the renderer will ever get updated, or just because you can easily import the WMO back and rebake the colors. --- [[User:Skarn|Skarn]] ([[User talk:Skarn|talk]])<br />
<br />
== MODS chunk ==<br />
<br />
*'''This chunk defines doodad sets.''' <br />
<br />
Doodads in WoW are [[M2]] model files. There are 32 bytes per doodad set, and nSets entries. Doodad sets specify several versions of "interior decoration" for a [[WMO]]. Like, a small house might have tables and a bed laid out neatly in one set, and have a horrible mess of abandoned broken things in another set called "Set_Abandoned01".<br />
<br />
Sets are exclusive except for the very first one, "Set_$DefaultGlobal" which is additive and is always displayed. The client determines that set by index, not name though. Up to 8 doodad sets can be enabled at the same time, e.g. via destructible buildings or garrisons.<br />
<br />
The doodad set number for every WMO instance is specified in the [[ADT]] files, or via DBC or via game object fields, depending on how it is spawned.<br />
<br />
struct SMODoodadSet<br />
{<br />
/*0x00*/ char name[0x14]; // set name, informational<br />
/*0x14*/ uint32_t startIndex; // index of first doodad instance in this set, into [[MODD|#MODD_chunk]] directly.<br />
/*0x18*/ uint32_t count; // number of doodad instances in this set<br />
/*0x1C*/ char pad[4];<br />
/*0x20*/<br />
} doodadSetList[];<br />
<br />
== MODN chunk ==<br />
<br />
*'''List of filenames for [[M2]] ([[MDX|mdx]]) models that appear in this [[WMO]].''' <br />
A block of zero-padded, zero-terminated strings. There are nModels file names in this list. They have to be .[[MDX]]!<br />
<br />
char doodadNameList[];<br />
<br />
== MODI chunk ==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note=Replaces filenames in [[#MODN chunk|MODN]]}}<br />
<br />
uint32_t doodadFileIDs[];<br />
<br />
== MODD chunk ==<br />
<br />
*'''Information for doodad instances. 40 bytes per doodad instance, nDoodads entries.''' <br />
<br />
-- There are not nDoodads entries here! Divide the chunk length by 40 to get the correct amount.<br />
<br />
While [[WMO]]s and models ([[M2]]s) in a map tile are rotated along the axes, doodads within a [[WMO]] are oriented using quaternions! Hooray for consistency!<br />
<br />
I had to do some tinkering and mirroring to orient the doodads correctly using the quaternion, see model.cpp in the WoWmapview source code for the exact transform matrix. It's probably because I'm using another coordinate system, as a lot of other coordinates in [[WMO]]s and models also have to be read as (X,Z,-Y) to work in my system. But then again, the [[ADT]] files have the "correct" order of coordinates. Weird.<br />
<br />
struct SMODoodadDef<br />
{<br />
/*000h*/ uint32_t nameIndex : 24; // reference offset into [[WMO#MODN_chunk|MODN]], or [[WMO#MODI_chunk|MODI]], depending on version and presence.<br />
/*003h*/ uint32_t flag_AcceptProjTex : 1;<br />
/*003h*/ uint32_t flag_0x2 : 1; // MapStaticEntity::field_34 |= 1 (if set, MapStaticEntity::AdjustLighting is _not_ called)<br />
/*003h*/ uint32_t flag_0x4 : 1;<br />
/*003h*/ uint32_t flag_0x8 : 1;<br />
/*003h*/ uint32_t : 4; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} position; // (X,Z,-Y)<br />
/*010h*/ {{Template:Type|C4Quaternion}} orientation; // (X, Y, Z, W)<br />
/*020h*/ float scale; // scale factor<br />
/*024h*/ {{Template:Type|CImVector}} color; // (B,G,R,A) overrides pc_sunColor<br />
} doodadDefList[];<br />
<br />
It looks like in order to get correct picture the color from SMODoodadDef should be applied only to opaque submeshes of M2. [[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) <br />
<br />
<br />
* How to compute a matrix to map WMO's M2 to world coordinates<br />
<br />
The coordinate system here is WMO's local coordinate system. It's Z-up already, that differs it from Y-up in '''[[ADT#MODF_chunk|MODF(ADT)]]''', '''[[WDT#MODF_chunk|MODF(WDT)]]''' and '''[[ADT#MDDF_chunk|MDDF]]''' chunks.<br />
To compute the whole placement matrix for doodad you would need take positionMatrix of WMO from '''[[ADT#MODF_chunk|MODF(ADT)]]''' or '''[[WDT#MODF_chunk|MODF(WDT)]]''' and multiply it by positionMatrix calculated here.<br />
<br />
Example implementation in js with gl-matrix library:<br />
<syntaxhighlight lang="javascript"><br />
function createPlacementMatrix(modd, wmoPlacementMatrix){<br />
var placementMatrix = mat4.create();<br />
mat4.identity(placementMatrix);<br />
mat4.multiply(placementMatrix, placementMatrix, wmoPlacementMatrix);<br />
<br />
mat4.translate(placementMatrix, placementMatrix, [modd.pos[0],modd.pos[1], modd.pos[2]]);<br />
<br />
var orientMatrix = mat4.create();<br />
mat4.fromQuat(orientMatrix,<br />
[modd.rotation[0], //imag.x<br />
modd.rotation[1], //imag.y,<br />
modd.rotation[2], //imag.z,<br />
modd.rotation[3] //real<br />
]<br />
);<br />
mat4.multiply(placementMatrix, placementMatrix, orientMatrix);<br />
<br />
mat4.scale(placementMatrix, placementMatrix, [modd.scale, modd.scale, modd.scale]);<br />
return placementMatrix;<br />
}<br />
</syntaxhighlight><br />
<br />
== MFOG chunk ==<br />
<br />
*'''Fog information. Made up of blocks of 48 bytes.'''<br />
<br />
struct SMOFog<br />
{<br />
/*000h*/ uint32_t flag_infinite_radius : 1; // F_IEBLEND: Ignore radius in CWorldView::QueryCameraFog<br />
/*000h*/ uint32_t : 3; // unused as of 7.0.1.20994<br />
/*000h*/ uint32_t flag_0x10 : 1;<br />
/*000h*/ uint32_t : 27; // unused as of 7.0.1.20994<br />
/*004h*/ {{Template:Type|C3Vector}} pos;<br />
/*010h*/ float smaller_radius; // start<br />
/*014h*/ float larger_radius; // end<br />
enum EFogs <br />
{<br />
FOG,<br />
UWFOG, // uw = under water<br />
NUM_FOGS,<br />
};<br />
struct Fog<br />
{<br />
float end;<br />
float start_scalar; // (0..1) -- minimum distance is end * start_scalar<br />
{{Template:Type|CImVector}} color; // The back buffer is also cleared to this colour<br />
/*018h*/ } fogs[NUM_FOGS];<br />
} fogList[];<br />
<br />
*Fog end: This is the distance at which all visibility ceases, and you see no objects or terrain except for the fog color.<br />
*Fog start: This is where the fog starts. Obtained by multiplying the fog end value by the fog start multiplier.<br />
<br />
*There should always be at least one fog entry in MFOG. The empty fog entry has both radiuses set to zero, 444.4445 for end, 0.25 for start_scalar, 222.2222 for underwater end, -0.5 for underwater start_scalar.<br />
<br />
*F_IEBLEND - InteriorExteriorBlend<br />
:These fog entries are used to reduce fog visibility based on the player's proximity i.e. the closer you are, the less on-screen fog. They are usually placed near exits to prevent fog showing in unintended places such as behind instance portals (e.g. Stockades fog showing on the Stormwind side of the portal). Whilst not being rendered they are still computed; the resulting blend percentage is applied as a multiplier (<code>1.0 - ComputedBlendPercentage</code>) to the scalar and colour calculations of the area fog.<br />
:This fog ignores all visibility checks (so that the multiplier is always applied) and is excluded from fog queries. Only one is used per <tt>mapObjGroup->fogList</tt> with the last taking precedence. (verified {{Template:Sandbox/VersionRange|max_expansionlevel=3}})<br />
<br />
== MCVP chunk (optional) ==<br />
<br />
*'''Convex Volume Planes. Contains blocks of floating-point numbers.''' 0x10 bytes (4 floats) per entry.<br />
<br />
{{Template:Type|C4Plane}} convexVolumePlanes[]; // normal points out<br />
<br />
These are used to define the volume of when you are inside this WMO. Important for transports. If a point is behind all planes (i.e. point-plane distance is negative for all planes), it is inside.<br />
<br />
==GFID==<br />
{{SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
* required when WMO is load from fileID (e.g. game objects)<br />
struct {<br />
uint32 id[MOHD.nGroups];<br />
} groupFileDataIDs[ !MOHD.Flag_Lod ? 1<br />
: MOHD.numLod ? MOHD.numLod : 3 // fallback for missing numLod: assume numLod=2+1base<br />
];<br />
<br />
==MDDI==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
==MPVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MPVD {<br />
// Unknown<br />
} particulateVolumes[];<br />
<br />
==MAVG==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
Same structure as MAVD, except pos/start/end are 0 values because this is a global ambient, but they are still written in the file<br />
<br />
struct MAVG {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1;<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color1 and color3<br />
/*022h*/ uint16_t doodadSetID;<br />
/*028h*/ char _0x28[8];<br />
} globalAmbientVolumes[];<br />
<br />
{{Template:Unverified|WMO base ambient color is now determined from the following:}}<br />
* if MAVG exists use the entry with matching doodadSetID else MAVG[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1 {{Template:Unverified|(secondary ambient color?)}}<br />
** else entry.color1<br />
* else if MAVD exists use MAVD[0]<br />
** if (entry.flags & 1) then use entry.color3 and entry.color1<br />
** else entry.color1<br />
* else use MOHD.ambColor<br />
<br />
==MAVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MAVD {<br />
/*000h*/ {{Type|C3Vector}} pos;<br />
/*00Ch*/ float start;<br />
/*010h*/ float end;<br />
/*014h*/ {{Type|CImVector}} color1; // overrides MOHD.ambColor<br />
/*018h*/ {{Type|CImVector}} color2;<br />
/*01Ch*/ {{Type|CImVector}} color3;<br />
/*020h*/ uint32_t flags; // &1: use color2 and color3<br />
/*024h*/ uint16_t doodadSetId;<br />
/*026h*/ char _0x26[10];<br />
} ambientVolumes[];<br />
<br />
==MBVD==<br />
{{SectionBox/VersionRange|min_build=8.3.0.32044|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct MBVD {<br />
/*000h*/ {{Type|C4Plane}} _0x00[6]; // {{Template:Unverified|position + start}}<br />
/*060h*/ float end;<br />
/*064h*/ {{Type|CImVector}} color1;<br />
/*068h*/ {{Type|CImVector}} color2;<br />
/*06Ch*/ {{Type|CImVector}} color3;<br />
/*070h*/ uint32_t flags; // &1: use color2 + color3<br />
/*074h*/ uint16_t doodadSetId;<br />
/*076h*/ char _0x76[10];<br />
} ambientBoxVolumes[];<br />
<br />
Only read if a MAVG or MAVD chunk exists.<br />
<br />
==MFED==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MFED {<br />
char unk[0x10];<br />
} mfed[];<br />
<br />
==MGI2==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MGI2 {<br />
/*0x00*/ char _0x0[4];<br />
/*0x04*/ uint32_t lodIndex; // groupInfoList[i].flags & SMOGroup::LOD<br />
/*0x08*/<br />
} mapobject_group_info_v2[];<br />
{{Template:Unverified|Used to explicitly control what groups use what level lod}}. If present, overrides the previous lod loading implementation.<br />
<br />
Requires exact same count as MOGI. Or replacement of it? Probably replacmeent.<br />
<br />
==MNLD==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MNLD {<br />
int typeRelated; // Known: 0, 1<br />
int indexRelated; // Appears to be same as index in mapobject_new_light_defs[]<br />
char gap4[4];<br />
short indexRelated2; // Into other struct<br />
char gapE[6];<br />
C3Vector unsigned14;<br />
C3Vector unsigned20;<br />
float float0;<br />
float float30;<br />
float float1;<br />
char gap34[16];<br />
C3Vector field_48;<br />
C3Vector field_54;<br />
char gap60[28];<br />
float field_7c;<br />
float field_80;<br />
float field_84;<br />
char gap88[45];<br />
char field_50;<br />
char unk2[2];<br />
} mapobject_new_light_defs[];<br />
<br />
==MDDL==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct MDDL {<br />
/*0x00*/ float _0x0; // offset?<br />
/*0x04*/ uint16_t layerCount;<br />
struct {<br />
uint8_t a;<br />
uint8_t count2;<br />
struct {<br />
uint32_t a;<br />
uint8_t b;<br />
} whats?[count2];<br />
} layers?[layerCount];<br />
struct {<br />
uint16_t a;<br />
uint32_t b;<br />
} whatwhats?[????];<br />
} detailDoodadLayers[];<br />
<br />
= WMO group file =<br />
<br />
WMO group files contain the actual polygon soup for a particular section of the entire [[WMO]].<br />
<br />
Every group file has one top-level [[WMO#MOGP_chunk|MOGP]] chunk, that has a 68-byte header followed by more subchunks. So it can be effectively treated as a file with a header at 0x14 and chunks starting at 0x58. <br />
<br />
The subchunks are not always present. Some are fixed and needed while others are only checked for if some flags in the header are set. The chunks '''need''' to be in the right order if you want WoW to read it.<br />
<br />
The following chunks are always present in the following order:<br />
*[[WMO#MOGP_chunk|MOGP]]<br />
*[[WMO#MOPY_chunk|MOPY]]<br />
*[[WMO#MOVI_chunk|MOVI]]<br />
*[[WMO#MOVT_chunk|MOVT]]<br />
*[[WMO#MONR_chunk|MONR]]<br />
*[[WMO#MOTV_chunk|MOTV]]<br />
*[[WMO#MOBA_chunk|MOBA]]<br />
<br />
These chunks are only present if a flag in the header is set. See the list below for the flags.<br />
*Cataclysm introduced a new optional MOBS chunk, I guess it's related to [[WMO#MOBA_chunk|MOBA]]. ---[[User:Bananenbrot|Bananenbrot]], 12-18-2010<br />
*[[WMO#MOLR_chunk|MOLR]]<br />
*[[WMO#MODR_chunk|MODR]]<br />
*[[WMO#MOBN_chunk|MOBN]]<br />
*[[WMO#MOBR_chunk|MOBR]]<br />
*MPBV<br />
*MPBP<br />
*MPBI<br />
*MPBG<br />
*[[WMO#MOCV_chunk|MOCV]]<br />
*[[WMO#MLIQ_chunk|MLIQ]]<br />
*[[WMO#MORI|MORI]]<br />
*[[WMO#MORB|MORB]]<br />
* [[WMO#MOTV_chunk|MOTV]] 2<br />
* [[WMO#MOCV_chunk|MOCV]] 2<br />
<br />
== MOGP chunk ==<br />
<br />
'''IMPORTANT''': This chunk contains all other chunks! The following variables are a header only. The MOGP chunk size will be way more than the header variables!<br />
<br />
struct {<br />
/*0x00*/ uint32_t groupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x04*/ uint32_t descriptiveGroupName; // offset into [[#MOGN_chunk|MOGN]]<br />
/*0x08*/ uint32_t flags; // see below<br />
/*0x0C*/ {{Template:Type|CAaBox}} boundingBox; // as with flags, same as in corresponding [[#MOGI_chunk|MOGI]] entry<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint32_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
uint32_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#else<br />
/*0x24*/ uint16_t portalStart; // index into [[#MOPR_chunk|MOPR]]<br />
/*0x26*/ uint16_t portalCount; // number of [[#MOPR_chunk|MOPR]] items used after portalStart<br />
#endif<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x28*/ uint16_t transBatchCount;<br />
/*0x2A*/ uint16_t intBatchCount;<br />
/*0x2C*/ uint16_t extBatchCount;<br />
/*0x2E*/ uint16_t padding_or_batch_type_d; // probably padding, but might be data?<br />
#endif <br />
<br />
/*0x30*/ uint8_t fogIds[4]; // ids in [[#MFOG_chunk|MFOG]]<br />
/*0x34*/ uint32_t groupLiquid; // see below in the [[#MLIQ_chunk|MLIQ]] chunk<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
SMOGxBatch intBatch[4];<br />
SMOGxBatch extBatch[4];<br />
#endif<br />
<br />
/*0x38*/ {{Template:Type/foreign_key|table=WMOAreaTable|column=m_WMOGroupID}} uniqueID;<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t padding[8];<br />
#else<br />
enum<br />
{<br />
flag2_CanCutTerrain = 1, // {{Template:Sandbox/VersionRange|min_expansionlevel=5}} has [[#MOPL_.28WoD.28.3F.29.2B.29|portal planes]] to cut<br />
};<br />
/*0x3C*/ uint32_t flags2;<br />
/*0x40*/ uint32_t unk; // UNUSED: 20740<br />
#endif<br />
} map_object_group_header;<br />
// remaining chunks follow<br />
<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
struct SMOGxBatch<br />
{<br />
uint16_t vertStart;<br />
uint16_t vertCount;<br />
uint16_t batchStart;<br />
uint16_t batchCount;<br />
};<br />
#endif<br />
<br />
The fields referenced from the [[WMO#MOPR_chunk|MOPR]] chunk indicate portals leading out of the [[WMO]] group in question.<br />
<br />
For the "Number of batches" fields, <code>transBatchCount</code> + <code>intBatchCount</code> + <code>extBatchCount</code> == the total number of batches in the [[WMO]] group (in the [[#MOBA_chunk|MOBA]] chunk). This might be some kind of LOD thing, or just separating the batches into different types/groups…?<br />
<br />
Flags: always contain more information than flags in [[#MOGI_chunk|MOGI]]. I suppose [[#MOGI_chunk|MOGI]] only deals with topology/culling, while flags here also include rendering info.<br />
<br />
===group flags===<br />
'''Flag Meaning'''<br />
0x1 Has BSP tree ([[#MOBN_chunk|MOBN]] and [[#MOBR_chunk|MOBR]] chunk).<br />
0x2 Has light map ([[#MOLM|MOLM]], [[#MOLD|MOLD]]). (UNUSED: 20740) possibly: subtract mohd.color in mocv fixing <br />
0x4 Has vertex colors ([[#MOCV_chunk|MOCV]] chunk).<br />
0x8 SMOGroup::EXTERIOR -- Outdoor - also influences how doodads are culled<br />
0x10 (UNUSED: 20740)<br />
0x20 (UNUSED: 20740)<br />
0x40 SMOGroup::EXTERIOR_LIT -- "Do not use local diffuse lightning". Applicable for both doodads from this wmo group(color from MODD) and water(CWorldView::GatherMapObjDefGroupLiquids). <br />
0x80 SMOGroup::UNREACHABLE<br />
0x100 Show exterior sky in interior WMO group (Used for interiors of city in stratholme_past.wmo)<br />
0x200 Has lights ([[#MOLR_chunk|MOLR]] chunk)<br />
0x400 <= Cataclysm: Has [[#MPBV|MPBV]], [[#MPBP|MPBP]], [[#MPBI|MPBI]], [[#MPBG|MPBG]] chunks, neither 0.5.5, 3.3.5a nor Cataclysm alpha actually use them though, but just skips them. Legion+(?): SMOGroup::LOD: Also load for LoD != 0 (_lod* groups)<br />
0x800 Has doodads ([[#MODR_chunk|MODR]] chunk)<br />
0x1000 SMOGroup::LIQUIDSURFACE -- Has water ([[#MLIQ_chunk|MLIQ]] chunk)<br />
0x2000 SMOGroup::INTERIOR -- Indoor<br />
0x4000 (UNUSED: 20740)<br />
0x8000 QueryMountAllowed in 0.5.3<br />
0x10000 SMOGroup::ALWAYSDRAW -- clear 0x8 after CMapObjGroup::Create() in MOGP and MOGI<br />
0x20000 (UNUSED: 20740) Has [[WMO#MORI|MORI]] and [[WMO#MORB|MORB]] chunks.<br />
0x40000 Show skybox -- automatically unset if MOSB not present.<br />
0x80000 is_not_water_but_ocean, LiquidType related, see below in the MLIQ chunk.<br />
0x100000<br />
0x200000 IsMountAllowed<br />
0x400000 (UNUSED: 20740)<br />
0x800000<br />
0x1000000 SMOGroup::CVERTS2: Has the second [[#MOCV_chunk|MOCV]] chunks: If the flag 0x4 isn't set this is the only MOCV chunk in the group. Whether the flag 0x4 is set or not: only the alpha values from this chunk are used (to blend the textures). '''[[#CMapObjGroup::FixColorVertexAlpha|FixColorVertexAlpha]] must not be used on this chunk !'''<br />
0x2000000 SMOGroup::TVERTS2: Has two [[#MOTV_chunk|MOTV]] chunks: Just add two.<br />
0x4000000 SMOGroup::ANTIPORTAL: Just call CMapObjGroup::CreateOccluders() independent of groupname being "antiportal". requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x8000000 unk. requires intBatchCount == 0, extBatchCount == 0, UNREACHABLE.<br />
0x10000000 (UNUSED: 20740)<br />
0x20000000 {{Template:Unverified|>> 20740}} SMOGroup::EXTERIOR_CULL<br />
0x40000000 SMOGroup::TVERTS3: Has three [[#MOTV_chunk|MOTV]] chunks, eg. for [[#MOMT_chunk|MOMT]] with shader 18.<br />
0x80000000 Seen in world/wmo/kultiras/human/8hu_warfronts_armory_v2_000.wmo<br />
vv flags2<br />
0x01???????? canCutTerrain<br />
0x30000000 SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH as per "(m_groupFlags & (SMOGroup::depSHADOWMAPGEN | SMOGroup::depSHADOWMAPGEN_DEPTH)) == 0" and *(_DWORD *)(a1 + 36) & 0x30000000. yes, this clashes with EXTERIOR_CULL, but that's in the same version. weird.<br />
<br />
=== "antiportal" ===<br />
<br />
If a group wmo is named "antiportal", CMapObjGroup::CreateOccluders() is called and group flags 0x4000000 and 0x80 are set automatically in both, MOGP and MOGI. Also, the BSP tree is cleared and batch_count[interior] and [exterior] is set to 0. If flags & 0x4000000 is set, just CMapObjGroup::CreateOccluders() is called, without setting flags or clearing bsp.<br />
<br />
m_vertices is content of MOVT<br />
<br />
void CMapObjGroup::CreateOccluders()<br />
{<br />
for ( unsigned int mopy_index (0), movi_index (0)<br />
; mopy_index < this->mopy_count<br />
; ++mopy_index, ++movi_index<br />
) <br />
{<br />
{{Template:Type|C3Vector}}* points[3] = <br />
{ &this->m_vertices[this->movi[3*mopy_index + 0]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 1]]<br />
, &this->m_vertices[this->movi[3*mopy_index + 2]]<br />
};<br />
<br />
float avg ((points[0]->z + points[1]->z + points[2]->z) / 3.0); <br />
<br />
unsigned int two_points[2];<br />
unsigned int two_points_index (0);<br />
<br />
for (unsigned int i (0); i < 3; ++i)<br />
{<br />
if (points[i]->z > avg)<br />
{<br />
two_points[two_points_index++] = i;<br />
}<br />
}<br />
<br />
if (two_points_index > 1)<br />
{<br />
CMapObjOccluder* occluder (CMapObj::AllocOccluder());<br />
occluder->p1 = points[two_points[0]];<br />
occluder->p2 = points[two_points[1]];<br />
<br />
append (this->occluders, occluder);<br />
}<br />
}<br />
}<br />
<br />
== MOPY chunk ==<br />
<br />
*'''Material info for triangles, two bytes per triangle. So size of this chunk in bytes is twice the number of triangles in the WMO group.'''<br />
<br />
struct SMOPoly<br />
{<br />
struct<br />
{<br />
/*0x01*/ uint8_t F_UNK_0x01: 1;<br />
/*0x02*/ uint8_t F_NOCAMCOLLIDE : 1;<br />
/*0x04*/ uint8_t F_DETAIL : 1;<br />
/*0x08*/ uint8_t F_COLLISION : 1; // Turns off rendering of water ripple effects. May also do more. Should be used for ghost material triangles.<br />
/*0x10*/ uint8_t F_HINT : 1;<br />
/*0x20*/ uint8_t F_RENDER : 1;<br />
/*0x40*/ uint8_t F_UNK_0x40 : 1;<br />
/*0x80*/ uint8_t F_COLLIDE_HIT : 1;<br />
<br />
bool isTransFace() { return F_UNK_0x01 && (F_DETAIL || F_RENDER); } // triangles flagged as TRANSITION. These triangles blend lighting from exterior to interior<br />
bool isColor() { return !F_COLLISION; }<br />
bool isRenderFace() { return F_RENDER && !F_DETAIL; }<br />
bool isCollidable() { return F_COLLISION || isRenderFace(); }<br />
} flags;<br />
<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t lightmapTex; // index into [[#MOLD|MOLD]]<br />
#endif<br />
uint8_t material_id; // index into [[#MOMT_chunk|MOMT]], 0xff for collision faces<br />
#if version {{Template:Sandbox/VersionRange|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t padding;<br />
#endif<br />
} polyList[];<br />
<br />
0xFF is used for collision-only triangles. They aren't rendered but have collision. Problem with it: WoW seems to cast and reflect light on them. Its a bug in the engine. --[[User:Schlumpf|schlumpf_]] 20:40, 7 June 2009 (CEST)<br />
<br />
Triangles stored here are more-or-less pre-sorted by texture, so it's ok to draw them sequentially.<br />
<br />
== MOVI chunk ==<br />
<br />
*'''Vertex indices for triangles.''', count = size / sizeof(unsigned short). Three 16-bit integers per triangle, that are indices into the vertex list. The numbers specify the 3 vertices for each triangle, their order makes it possible to do backface culling.<br />
<br />
uint16_t indexList[];<br />
<br />
== MOVX chunk ==<br />
Possible replacement for MOVI chunk but allowing for larger indices (uint vs MOVI's ushort)? Spotted in 9.0, but might have existed for a while.<br />
<br />
== MOVT chunk ==<br />
<br />
*'''Vertices chunk.''', count = size / (sizeof(float) * 3). 3 floats per vertex, the coordinates are in (X,Z,-Y) order. It's likely that [[WMO]]s and models ([[M2]]s) were created in a coordinate system with the Z axis pointing up and the Y axis into the screen, whereas in OpenGL, the coordinate system used in WoWmapview the Z axis points toward the viewer and the Y axis points up. Hence the juggling around with coordinates.<br />
<br />
C3Vector vertexList[];<br />
<br />
== MONR chunk ==<br />
<br />
*'''Normals.''' count = size / (sizeof(float) * 3). 3 floats per vertex normal, in (X,Z,-Y) order.<br />
<br />
C3Vector normalList[];<br />
<br />
== MOTV chunk ==<br />
<br />
*'''Texture coordinates, 2 floats per vertex in (X,Y) order.''' The values usually range from 0.0 to 1.0, but it's ok to have coordinates out of that range. Vertices, normals and texture coordinates are in corresponding order, of course. Not present in [[WMO#.22antiportal.22|antiportal]] WMO groups.<br />
<br />
C2Vector textureVertexList[]; // ranging [0, 1], can be outside that range though and will be normalised.<br />
<br />
''Client loads multiple MOTV chunks into an array but only keeps the count of the last one. This behavior is different to all other chunk types read. The array has 3 entries, after that the client will overwrite its data structures, starting with the MOTV_Counter field itself. (checked with client 29297, client 30918 still has this severe bug)''<br />
<br />
else // MOTV<br />
{<br />
this->MOTV[this->MOTV_Counter++] = Chuck->Payload; // careful, unchecked array access<br />
this->MOTV_Count = Chunk->Length >> 3;<br />
}<br />
<br />
==MOLV==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk is referenced by [[#MOPY_chunk|MOPY]] index with 3 entries per SMOPoly.<br />
C2Vector lightmapVertexList[];<br />
<br />
==MOIN==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
uint16_t indexList[];<br />
<br />
It's most of the time only a list incrementing from <code>0</code> to <code>nFaces * 3</code> or less, not always up to <code>nPolygons</code> (calculated with [[#MOPY_chunk|MOPY]]).<br />
<br />
Unlike in {{Template:Sandbox/VersionRange|min_expansionlevel=1}} where the faces indices ([[#MOVI_chunk|MOVI]]) point to a vertex in [[#MOVT_chunk|MOVT]], here there are exactly <code>nFaces * 3</code> vertices in [[#MOVT_chunk|MOVT]], and the client just read them straightforward. If you want to read them, just make <code>nPolygons</code> faces going incrementing, like <code>(0, 1, 2), (3, 4, 5), …</code> --Gamhea 15:44, 10 March 2013 (UTC)<br />
<br />
== MOBA chunk ==<br />
<br />
*'''Render batches. Records of 24 bytes.'''<br />
<br />
struct SMOBatch<br />
{<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint8_t lightMap; // index into [[#MOLM|MOLM]]<br />
uint8_t texture; // index into [[#MOMT_chunk|MOMT]]<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=7|max_exclusive=1}}<br />
/*0x00*/ int16_t bx, by, bz; // a bounding box for culling, see "unknown_box" below<br />
/*0x06*/ int16_t tx, ty, tz;<br />
#else<br />
/*0x00*/ uint8_t unknown[0xA];<br />
/*0x0A*/ uint16_t material_id_large; // used if flag_use_uint16_t_material is set.<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494}} <br />
uint16_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#else<br />
/*0x0C*/ uint32_t startIndex; // index of the first face index used in [[#MOVI_chunk|MOVI]]<br />
#endif<br />
/*0x10*/ uint16_t count; // number of [[#MOVI_chunk|MOVI]] indices used<br />
/*0x12*/ uint16_t minIndex; // index of the first vertex used in [[#MOVT_chunk|MOVT]]<br />
/*0x14*/ uint16_t maxIndex; // index of the last vertex used (batch includes this one)<br />
/*0x16*/ uint8_t flag_unknown_1 : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
/*0x16*/ uint8_t flag_use_material_id_large : 1; // instead of material_id use material_id_large<br />
#endif<br />
// F_RENDERED = 0xf0, so probably upper nibble isn't unused<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592}} <br />
/*0x17*/ uint8_t material_id; // index in [[#MOMT_chunk|MOMT]]<br />
#else<br />
uint8_t padding;<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.6.0.3592|max_expansionlevel=1|max_exclusive=1}} <br />
uint8_t unknown[8]; // always 0 filled<br />
#endif<br />
} batchList[];<br />
<br />
Batches are groups of faces with the same material ID in root's MOMT, and they're used to accelerate rendering. Note that the client doesn't use them in the same way while rendering in D3D or OpenGL (only D3D uses all batches information). The vertex buffer containing vertices from <code>minIndex</code> to <code>maxIndex</code> can contain vertices that aren't used by the batch. On the other hand, if one of the faces used need a vertex, it has to be in the buffer. <del>Concerning the byte at 0x16, as a material ID is coded on a uint8, I guess it is completely unused.</del><br />
--[[User:Gamhea|Gamhea]] 12:23, 29 July 2013 (UTC)<br />
<br />
===unknown_box===<br />
This is a very low resolution bounding box of the contained vertices. The client appears to be using them to do batch-level culling, so if they are set incorrectly, the batch may be randomly disappearing. According to [[User:Adspartan|Adspartan]] ([[User talk:Adspartan|talk]]), the box can be calculated by just iterating over all vertices contained (by following <code>minIndex</code> and <code>maxIndex</code> to [[#MOVT|MOVT]] and taking the minimum/maximum of those. They should probably be rounded away from zero instead of being truncated on conversion to <code>int16_t</code>. <br />
<br />
{{Template:SectionBox|This section only applies to version {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}}}}<br />
In the 0.5.3 Alpha this box is used for batch-level culling. The values are converted to a {{Template:Type|CAaBox}} inside <code>CMapObj::CullBatch</code>, by being directly cast to floats, this box is then passed to <code>CWorldScene::FrustumCull</code> for rendering.<br />
<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7}}<br />
<br />
<code>unknown_box</code> seems no longer used (and nulled). Instead, <code>flag_use_material_id_large</code> can be set to use <code>material_id_large</code> which was the last of <code>unknown_box</code>'s fields. This means that when "retroporting" files, <code>unknown_box</code>'s values need to be calculated (by building minimum and maximum from the corresponding vertices) and <code>material_id</code> should be set, if it can fit a <code>uint8_t</code>. --based on [[User:Rangorn|Rangorn]] ([[User talk:Rangorn|talk]])<br />
<br />
== MOLR chunk ==<br />
<br />
*'''Light references, one 16-bit integer per light reference.'''<br />
<br />
uint16_t lightRefList[];<br />
<br />
This is basically a list of lights used in this [[WMO]] group, the numbers are indices into the [[WMO]] root file's [[WMO#MOLT_chunk|MOLT]] table.<br />
<br />
For some [[WMO]] groups there is a large number of lights specified here, more than what a typical video card will handle at once. I wonder how they do lighting properly. Currently, I just turn on the first GL_MAX_LIGHTS and hope for the best. :(<br />
<br />
== MODR chunk ==<br />
<br />
*'''Doodad references, one 16-bit integer per doodad.'''<br />
<br />
uint16_t doodadRefList[];<br />
<br />
The numbers are indices into the doodad instance table ([[WMO#MODD_chunk|MODD]] chunk) of the [[WMO]] root file. These have to be filtered to the doodad set being used in any given [[WMO]] instance.<br />
<br />
== MOBN chunk ==<br />
<br />
*'''Nodes of the BSP tree, used for collision (along with bounding boxes ?). Array of t_BSP_NODE. / CAaBspNode.''' 0x10 bytes.<br />
<br />
enum Flags<br />
{<br />
Flag_XAxis = 0x0,<br />
Flag_YAxis = 0x1,<br />
Flag_ZAxis = 0x2,<br />
Flag_AxisMask = 0x3,<br />
Flag_Leaf = 0x4,<br />
Flag_NoChild = 0xFFFF,<br />
};<br />
<br />
struct CAaBspNode<br />
{ <br />
uint16_t flags; // See above enum. 4: leaf, 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane<br />
int16_t negChild; // index of bsp child node (right in this array)<br />
int16_t posChild;<br />
uint16_t nFaces; // num of triangle faces in [[WMO#MOBR_chunk|MOBR]]<br />
uint32_t faceStart; // index of the first triangle index(in [[WMO#MOBR_chunk|MOBR]])<br />
float planeDist;<br />
};<br />
<br />
planetype might be 0 for YZ-plane, 1 for XZ-plane, 2 for XY-plane, 4 for BSP leaf. fDist is where split plane locates based on planetype, ex, you have a planetype 0 and fDist 15, so the split plane is located at offset ( 15, 0, 0 ) with Normal as ( 1, 0, 0 ), I think the offset is relative to current node's bounding box center. The BSP root ( ie. node 0 )'s bounding box is the WMO's boundingbox, then you subdivide it with plane and fdist, then you got two children with two bounding box, and so on. you got the whole BSP tree. As the bsp leaf might overlapping the dividing plane, i think you might have two same face exist on two different bsp leaf. I'll make further tests to prove this. --[[mobius|mobius]].<br />
<br />
The biggest leaf in terms of number of faces in 3.3.5 contains more than 2100 faces (some ice giant in the Storm Peaks), so it's not advised to use more. (While I haven't investigated properly, there might be a limit at 8192 in 6.0.1.18179 --[[User:Schlumpf|Schlumpf]] ([[User talk:Schlumpf|talk]]) 11:18, 3 January 2016 (UTC))<br />
<br />
fDist is relative to point (0,0,0) of whole WMO. children[0] is child on negative side of dividing plane, children[1] is on positive side. --[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]]) 10:01, 15 January 2016 (UTC)<br />
<br />
<br />
#define epsilon 0.01F<br />
void MergeBox(CVect3 (&result)[2], float *box1, float *box2)'''<br />
{<br />
result[0][0] = box1[0];<br />
result[0][1] = box1[1];<br />
result[0][2] = box1[2];<br />
result[1][0] = box2[0];<br />
result[1][1] = box2[1];<br />
result[1][2] = box2[2];<br />
}<br />
void AjustDelta(CVect3 (&src)[2], float *dst, float coef)'''<br />
{<br />
float d1 = (src[1][0]- src[0][0]) * coef;// delta x<br />
float d2 = (src[1][1]- src[0][1]) * coef;// delta y<br />
float d3 = (src[1][2]- src[0][2]) * coef;// delta z<br />
dst[1] = d1 + src[0][1];<br />
dst[0] = d2 + src[0][0];<br />
dst[2] = d3 + src[0][2];<br />
}<br />
void TraverseBsp(int iNode, CVect3 (&pEyes)[2] , CVect3 (&pBox)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param)'''<br />
{<br />
int plane;<br />
float eyesmin_boxmin;<br />
float boxmax_eyesmax;<br />
float eyesmin_fdist;<br />
float eyes_max_fdist;<br />
float eyesmin_div_deltadist;<br />
CVect3 tBox1[2];<br />
CVect3 tBox2[2];<br />
CVect3 newEyes[2];<br />
CVect3 ajusted;<br />
T_BSP_NODE *pNode = &m_tNode[iNode];<br />
if ( pNode)<br />
{<br />
if (pNode->planetype & 4 )<br />
{<br />
if(pAction == 0)<br />
{<br />
RenderGeometry(GetEngine3DInstance(),pNode);<br />
return;<br />
}<br />
else<br />
{<br />
pAction(pNode,param);<br />
}<br />
}<br />
plane =pNode->planetype & 3;<br />
eyesmin_boxmin = pEyes[0][plane] - pBox[0][plane];<br />
if ( ( -epsilon < eyesmin_boxmin) | (-epsilon == eyesmin_boxmin) || (pEyes[1][plane]- pBox[0][plane]) >= -epsilon )<br />
{<br />
boxmax_eyesmax = pBox[1][plane] - pEyes[1][plane];<br />
if ( (epsilon < boxmax_eyesmax) | (epsilon == boxmax_eyesmax) || (pBox[1][plane] - pEyes[0][plane]) >= epsilon )<br />
{<br />
memmove(tBox1,pBox,sizeof(pBox));<br />
tBox1[0][plane] = pNode->fDist;<br />
memmove(tBox2,pBox,sizeof(pBox));<br />
tBox2[1][plane] = pNode->fDist;<br />
eyesmin_fdist = pEyes[0][plane] - pNode->fDist;<br />
eyes_max_fdist = (pEyes[1][plane]) - pNode->fDist;<br />
if ( eyesmin_fdist >= -epsilon && eyesmin_fdist <= epsilon|| (eyes_max_fdist >= -epsilon) && eyes_max_fdist <= epsilon )<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist > epsilon && eyes_max_fdist < epsilon)<br />
{<br />
if ( pNode->children[1] != (short)-1 ) TraverseBsp(pNode->children[1], pEyes, tBox1,pAction,param);<br />
return;<br />
}<br />
if ( eyesmin_fdist < -epsilon && eyes_max_fdist < -epsilon)<br />
{<br />
if ( pNode->children[0] != (short)-1 ) TraverseBsp(pNode->children[0] , pEyes, tBox2,pAction,param);<br />
return;<br />
}<br />
eyesmin_div_deltadist = (float)(eyesmin_fdist / (eyesmin_fdist - eyes_max_fdist));<br />
AjustDelta(pEyes, ajusted, eyesmin_div_deltadist);<br />
if ( eyesmin_fdist <= 0.0 )<br />
{<br />
if ( pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
if (pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
}<br />
else<br />
{<br />
if ( pNode->children[1] != (short)-1 )<br />
{<br />
MergeBox(newEyes, &pEyes[0][0], ajusted);<br />
TraverseBsp(pNode->children[1] , newEyes, tBox1,pAction,param);<br />
}<br />
if (pNode->children[0] != (short)-1 )<br />
{<br />
MergeBox(newEyes, ajusted, &pEyes[1][0]);<br />
TraverseBsp(pNode->children[0] , newEyes, tBox2,pAction,param);<br />
}<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
CheckFromEyes(CVect3 (&pEyes)[2],void *(pAction)(T_BSP_NODE *,void *param),void *param )<br />
{<br />
/*CVect3 eyes[2];<br />
instance_mat.invert();<br />
eyes[0] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,-10,0) );<br />
eyes[1] = _fixCoordSystemInv((instance_mat*p->m_pCameraViewport->GetCameraTarget())+CVect3(0,60,0) ); <br />
// make vector down<br />
*/<br />
/* eyes[0] = CVect3(-1.474797e+001F, -1.195053e+001F, 5.416779e+000F); // Debug absolute position from WP Azaroth 1164,58,-10645.83<br />
eyes[1] = CVect3(-1.474797e+001F, -1.195053e+001F, -1.754583e+003F);<br />
*/<br />
TraverseBsp(0,pEyes,m_bbox,pAction);<br />
}<br />
<br />
This BSP seems to be used for collision purpose only. <br />
<br />
An object could have has 2 collision system. The first one is encoded in a simplified Geometry (when MOPY. MaterialID=0xFF) the second one is encoded in T_BSP_NODE.<br />
Some object has collision method 1 only, some other uses method 2 only. Some object have both collision systems (some polygons are missing in the BSP but are present in the simplified geometry). how to use these 2 system remains unclear. <br />
<br />
For the time being, I check first the simplified geometry, and then if there is no collision, I apply a second pass using the BSP. It is sub-optimum, but it seems to work.<br />
Probably there is somewhere a flag telling us with which method we should use for the object.<br />
<br />
The code attached seems to work fine for BSP method--[[peter-pan|peter-pan]].<br />
<br />
== MOBR chunk ==<br />
<br />
*'''Face indices''' for CAaBsp ([[#MOBN_chunk|MOBN]]). Unsigned shorts.<br />
*'''Triangle indices (in [[WMO#MOVI_chunk|MOVI]] which define triangles) to describe polygon planes defined by [[WMO#MOBN_chunk|MOBN]] BSP nodes.'''<br />
<br />
uint16_t nodeFaceIndices[];<br />
<br />
Example code required to get an actual indices array from MOBR array:<br />
var bpsIndicies = new Array(mobr.length*3);<br />
for (var i = 0; i < mobr.length; i++) {<br />
bpsIndices[i*3 + 0] = movi[3*mobr[i]+0];<br />
bpsIndices[i*3 + 1] = movi[3*mobr[i]+1];<br />
bpsIndices[i*3 + 2] = movi[3*mobr[i]+2];<br />
}<br />
<br />
Example code to get indices into MOVT for triangles, referenced from BSP node definition:<br />
for (var triangleInd = node.firstFace; triangleInd<node.firstFace+node.numFaces; triangleInd++) {<br />
//3 vertices per triangle<br />
movt[bpsIndices[3*triangleInd + 0]]<br />
movt[bpsIndices[3*triangleInd + 1]]<br />
movt[bpsIndices[3*triangleInd + 2]]<br />
}<br />
<br />
== MOCV chunk ==<br />
<br />
*'''Vertex colors, 4 bytes per vertex (BGRA), for [[WMO]] groups using indoor lighting.''' <br />
<br />
CImVector colorVertexList[];<br />
<br />
I don't know if this is supposed to work together with, or replace, the lights referenced in [[WMO#MOLR_chunk|MOLR]]. But it sure is the only way for the ground around the goblin smelting pot to turn red in the Deadmines. (but some corridors are, in turn, too dark - how the hell does lighting work anyway, are there lightmaps hidden somewhere?)<br />
<br />
- I'm pretty sure WoW does not use lightmaps in it's [[WMO]]s...<br />
<br />
After further inspection, this is it, actual pre-lit vertex colors for [[WMO]]s - vertex lighting is turned off. This is used if flag 0x2000 in the [[WMO#MOGI_chunk|MOGI]] chunk is on for this group. This pretty much fixes indoor lighting in Ironforge and Undercity. The "light" lights are used only for [[M2]] models (doodads and characters). (The "too dark" corridors seemed like that because I was looking at it in a window - in full screen it looks pretty much the same as in the game) Now THAT's progress!!!<br />
<br />
''Yes, 0x2000 (INDOOR) flagged WMO groups use _only_ MOCV for lighting, however this chunk is also used to light outdoor groups as well like lantern glow on buildings, etc. If 0x8 (OUTDOOR) flag is set, you start out with normal world lighting (like with light db params) and then you multiply these vertex colors by the texture color and add it to the world lighting. This makes many models look much better. See the Forsaken buildings in Howling Fjord for an example of some that make use of this a lot for glowing windows and lamps. [[User:Relaxok|Relaxok]] 18:29, 20 March 2013 (UTC)''<br />
<br />
=== CMapObjGroup::FixColorVertexAlpha ===<br />
<br />
Prior to being passed to the shaders, MOCV values are manipulated by the CMapObj::FixColorVertexAlpha function in the client. This function performs different manipulations depending on the relationship between the vertex and the MOBA it appears in. It's possible that FixColorVertexAlpha did not always exist, or does not exist in later versions of WoW. It appears to have existed in WotLK, Cata, MoP, and WoD.<br />
<br />
In client versions that use FixColorVertexAlpha, without applying the function, certain parts of WMOs are noticeably wrong: fireplaces lack a glowing effect; the red light cast from bellows in blacksmith WMOs is undersaturated; etc.<br />
<br />
'''Warning:''' this should only be used for the "first" MOCV chunk which is referenced by the [[#group_flags|group flag 0x4]]. <br />
Regardless of whether this flag is set or not, this process should never be applied to the MOCV chunk referenced by the flag SMOGroup::CVERTS2 (0x1000000) as its purpose is different. Only the alpha values from that "second" chunk (which can be the only MOCV chunk present in the group file) are used for the purpose of blending the textures together.<br />
<br />
<br />
==== WMOs with MOHD->flags & 0x08 ====<br />
<br />
Only one manipulation takes place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
<br />
1. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== All other WMOs ====<br />
<br />
The following manipulations take place:<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[0] (aka unkBatchCount) are modified like so:<br />
1. Subtract MOHD->color[r|g|b]<br />
2. Subtract MOCV->color[r|g|b] * MOCV->color[a]<br />
3. Divide new MOCV->color[r|g|b] values by 2.0<br />
<br />
MOCVs matching vertices in MOGP->batchCounts[1] and MOGP->batchCounts[2] are modified like so:<br />
1. Subtract MOHD->color<br />
2. Add (MOCV->color[r|g|b] * MOCV->color[a]) >> 6<br />
3. Divide MOCV->color[r|g|b] values by 2.0<br />
4. If values are >= 0 and <= 255, keep value as is; else clamp new value to 0, 255.<br />
5. If MOGP.flags & 0x08, replace MOCV->color[a] with 255; else replace MOCV->color[a] with 0<br />
<br />
==== Decompiled code ====<br />
<br />
From build 18179, courtesy of schlumpf<br />
<br />
<pre><br />
void CMapObjGroup::FixColorVertexAlpha(CMapObjGroup *mapObjGroup)<br />
{<br />
int begin_second_fixup = 0;<br />
if ( mapObjGroup->unkBatchCount )<br />
{<br />
begin_second_fixup = mapObjGroup->moba[mapObjGroup->transBatchCount-1].maxIndex+ 1;<br />
}<br />
<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_has_some_outdoor_group )<br />
{<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
else<br />
{<br />
if ( mapObjGroup->m_mapObj->mohd->flags & flag_skip_base_color )<br />
{<br />
v35 = 0;<br />
v36 = 0;<br />
v37 = 0;<br />
}<br />
else<br />
{<br />
v35 = (mapObjGroup->m_mapObj->mohd.color >> 0) & 0xff;<br />
v37 = (mapObjGroup->m_mapObj->mohd.color >> 8) & 0xff;<br />
v36 = (mapObjGroup->m_mapObj->mohd.color >> 16) & 0xff;<br />
}<br />
<br />
for (int mocv_index (0); mocv_index < begin_second_fixup; ++mocv_index)<br />
{<br />
mapObjGroup->mocv[mocv_index].x -= v36;<br />
mapObjGroup->mocv[mocv_index].y -= v37;<br />
mapObjGroup->mocv[mocv_index].z -= v35;<br />
<br />
v38 = mapObjGroup->mocv[mocv_index].w / 255.0f;<br />
<br />
v11 = mapObjGroup->mocv[mocv_index].x - v38 * mapObjGroup->mocv[mocv_index].x;<br />
assert (v11 > -0.5f);<br />
assert (v11 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].x = v11 / 2;<br />
v13 = mapObjGroup->mocv[mocv_index].y - v38 * mapObjGroup->mocv[mocv_index].y;<br />
assert (v13 > -0.5f);<br />
assert (v13 < 255.5f);<br />
mapObjGroup->mocv[mocv_index].y = v13 / 2;<br />
v14 = mapObjGroup->mocv[mocv_index].z - v38 * mapObjGroup->mocv[mocv_index].z;<br />
assert (v14 > -0.5f);<br />
assert (v14 < 255.5f);<br />
mapObjGroup->mocv[mocv_index++].z = v14 / 2;<br />
}<br />
<br />
for (int i (begin_second_fixup); i < mapObjGroup->mocv_count; ++i)<br />
{<br />
v19 = (mapObjGroup->mocv[i].x * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].x - v36;<br />
mapObjGroup->mocv[i].x = std::min (255, std::max (v19 / 2, 0));<br />
<br />
v30 = (mapObjGroup->mocv[i].y * mapObjGroup->mocv[i].w) / 64 + mapObjGroup->mocv[i].y - v37;<br />
mapObjGroup->mocv[i].y = std::min (255, std::max (v30 / 2, 0));<br />
<br />
v33 = (mapObjGroup->mocv[i].w * mapObjGroup->mocv[i].z) / 64 + mapObjGroup->mocv[i].z - v35;<br />
mapObjGroup->mocv[i].z = std::min (255, std::max (v33 / 2, 0));<br />
<br />
mapObjGroup->mocv[i].w = mapObjGroup->m_groupFlags & SMOGroup::EXTERIOR ? 0xFF : 0x00;<br />
}<br />
}<br />
}<br />
</pre><br />
<br />
=== CMapObj::AttenTransVerts ===<br />
<br />
Similar to FixColorVertexAlpha above, the client will also run MOCV values through the CMapObj::AttenTransVerts function prior to rendering.<br />
<br />
In MoP and WoD, it appears that the client only runs AttenTransVerts in cases where flag 0x01 is NOT set on MOHD.flags.<br />
<br />
AttenTransVerts only modifies MOCV values for vertices in MOGP.batchCounts[0] (aka unkBatchCount) batches.<br />
<br />
The function iterates over all vertices in MOGP.batchCounts[0], and checks all portals for the group:<br />
* If no portals are found that lead to a group with MOGI.flags & (0x08 | 0x40), all MOCV alpha values are set to 0.0.<br />
* If a portal is found leading to a group with MOGI.flags & (0x08 | 0x40), each MOCV alpha is manipulated to be a range of 0.0 to 1.0 based on the distance of the corresponding vertex to the portal. Additionally, the RGB values for each MOCV are bumped by: (0.0 to 1.0) * (127 - existingRGB)<br />
<br />
==== Decompiled code ====<br />
<br />
void CMapObj::AttenTransVerts (CMapObj *mapObj, CMapObjGroup *mapObjGroup)<br />
{<br />
mapObjGroup->field_98 |= 1u;<br />
if (!mapObjGroup->unkBatchCount)<br />
{<br />
return;<br />
}<br />
<br />
for ( std::size_t vertex_index (0)<br />
; vertex_index < (*((unsigned __int16 *)&mapObjGroup->moba[(unsigned __int16)mapObjGroup->unkBatchCount] - 2) + 1)<br />
; ++vertex_index<br />
)<br />
{<br />
float opacity_accum (0.0);<br />
<br />
for ( std::size_t portal_ref_index (mapObjGroup->mogp->mopr_index)<br />
; portal_ref_index < (mapObjGroup->mogp->mopr_index + mapObjGroup->mogp->mopr_count)<br />
; ++portal_ref_index<br />
)<br />
{<br />
SMOPortalRef const& portalRef (mapObj->mopr[portal_ref_index]);<br />
SMOPortal const& portal (mapObj->mopt[portalRef.portalIndex]);<br />
C3Vector const& vertex (&mapObjGroup->movt[vertex_index]);<br />
<br />
float const portal_to_vertex (distance (portal.plane, vertex));<br />
<br />
C3Vector vertex_to_use (vertex);<br />
<br />
if (portal_to_vertex > 0.001 || portal_to_vertex < -0.001)<br />
{<br />
C3Ray ray ( C3Ray::FromStartEnd<br />
( vertex<br />
, vertex<br />
+ (portal_to_vertex > 0 ? -1 : 1) * portal.plane.normal<br />
, 0<br />
)<br />
);<br />
NTempest::Intersect<br />
(ray, &portal.plane, 0LL, &vertex_to_use, 0.0099999998);<br />
}<br />
<br />
float distance_to_use;<br />
<br />
if ( NTempest::Intersect ( vertex_to_use<br />
, &mapObj->mopv[portal.base_index]<br />
, portal.index_count<br />
, C3Vector::MajorAxis (portal.plane.normal)<br />
)<br />
)<br />
{<br />
distance_to_use = portalRef.side * distance (portal.plane, vertex);<br />
}<br />
else<br />
{<br />
distance_to_use = NTempest::DistanceFromPolygonEdge<br />
(vertex, &mapObj->mopv[portal.base_index], portal.index_count);<br />
}<br />
<br />
if (mapObj->mogi[portalRef.group_index].flags & 0x48)<br />
{<br />
float v25 (distance_to_use >= 0.0 ? distance_to_use / 6.0f : 0.0f);<br />
if ((1.0 - v25) > 0.001)<br />
{<br />
opacity_accum += 1.0 - v25;<br />
}<br />
}<br />
else if (distance_to_use > -1.0)<br />
{<br />
opacity_accum = 0.0;<br />
if (distance_to_use < 1.0)<br />
{<br />
break;<br />
}<br />
}<br />
}<br />
<br />
float const opacity ( opacity_accum > 0.001<br />
? std::min (1.0f, opacity_accum)<br />
: 0.0f<br />
);<br />
<br />
//! \note all assignments asserted to be > -0.5 && < 255.5f<br />
CArgb& color (mapObjGroup->mocv[vertex_index]);<br />
color.r = ((127.0f - color.r) * opacity) + color.r;<br />
color.g = ((127.0f - color.g) * opacity) + color.g;<br />
color.b = ((127.0f - color.b) * opacity) + color.b;<br />
color.a = opacity * 255.0;<br />
}<br />
}<br />
<br />
== MLIQ chunk ==<br />
<br />
*'''Specifies liquids inside WMOs.''' <br />
This is where the water from Stormwind and BFD etc. is hidden. (slime in Undercity, pool water in the Darnassus temple, some lava in IF)<br />
<br />
Chunk header:<br />
struct header<br />
{<br />
/*0x00*/ {{Template:Type|C2iVector}} liquidVerts; // number of vertices (x, y)<br />
/*0x08*/ {{Template:Type|C2iVector}} liquidTiles; // number of tiles (ntiles = nverts-1)<br />
/*0x10*/ {{Template:Type|C3Vector}} liquidCorner; // base coordinates for X and Y<br />
/*0x1C*/ uint16_t liquidMtlId; // material ID (index into [[#MOMT_chunk|MOMT]])<br />
}<br />
<br />
After the header, verts and tiles follow:<br />
<br />
struct SMOLVert<br />
{<br />
union<br />
{<br />
struct SMOWVert<br />
{<br />
uint8_t flow1;<br />
uint8_t flow2;<br />
uint8_t flow1Pct;<br />
uint8_t filler;<br />
float height;<br />
} waterVert;<br />
struct SMOMVert<br />
{<br />
int16_t s;<br />
int16_t t;<br />
float height;<br />
} magmaVert;<br />
};<br />
} liquidVertexList[xverts*yverts];<br />
<br />
struct SMOLTile<br />
{<br />
uint8_t liquid : 6;<br />
uint8_t fishable : 1;<br />
uint8_t shared : 1;<br />
} liquidTileList[xtiles*ytiles];<br />
<br />
The liquid data contains the vertex height map (xverts * yverts * 8 bytes) and the tile flags (xtiles * ytiles bytes) as described in [[ADT]] files ([[ADT#MCLQ_sub-chunk|MCLQ]] chunk). The length and width of a liquid tile is the same as on the map, that is, 1/8th of the length of a map chunk. (which is in turn 1/16th the length of a map tile).<br />
<br />
Note that although I could read Mh2o's heightmap and existstable in row major order (like reading a book), I had to read this one in column major order to compensate for a 90° misrotation. --[[User:Bananenbrot|Bananenbrot]] 22:02, 1 August 2012 (UTC)<br />
<br />
Either the unknown data or the "types" must somehow control how the points at the edges work. In looking at 3D mesh screen captures, something is changed to create a flat edge where it meets other MLIQ chunks. The first Unknown data is always 0 when a point isn't used. Other seen values: 1, 4, 12, 22, 27, 31, 105, & 124. Not yet sure what they mean/how to use them, I suspect they become the modifier for the edge placement points. --[[User:Kjasi|Kjasi]] 14 February 2016<br />
<br />
WMOs can have liquid in them even if MLIQ is not present! If MOGP.groupLiquid is set but no MLIQ is present or xtiles = 0 or ytiles = 0 then entire group is filled with liquid. In this case liquid height is equal to MOGP.boundingBox.max.z<br />
<br />
=== how to determine {{DBRef|table=LiquidType}} to use ===<br />
<br />
enum liquid_basic_types<br />
{<br />
liquid_basic_types_water = 0,<br />
liquid_basic_types_ocean = 1,<br />
liquid_basic_types_magma = 2,<br />
liquid_basic_types_slime = 3,<br />
<br />
liquid_basic_types_MASK = 3,<br />
};<br />
enum liquid_types<br />
{<br />
// ...<br />
LIQUID_WMO_Water = 13,<br />
LIQUID_WMO_Ocean = 14,<br />
LIQUID_Green_Lava = 15,<br />
LIQUID_WMO_Magma = 19,<br />
LIQUID_WMO_Slime = 20,<br />
<br />
LIQUID_END_BASIC_LIQUIDS = 20,<br />
LIQUID_FIRST_NONBASIC_LIQUID_TYPE = 21,<br />
<br />
LIQUID_NAXX_SLIME = 21,<br />
// ...<br />
};<br />
<br />
enum SMOGroup::flags<br />
{<br />
LIQUIDSURFACE = 0x1000,<br />
is_not_water_but_ocean = 0x80000,<br />
};<br />
<br />
liquid_types to_wmo_liquid (int x)<br />
{<br />
liquid_basic_types const basic (x & liquid_basic_types_MASK);<br />
switch (basic)<br />
{<br />
case liquid_basic_types_water:<br />
return (smoGroup->flags & is_not_water_but_ocean) ? LIQUID_WMO_Ocean : LIQUID_WMO_Water;<br />
case liquid_basic_types_ocean:<br />
return LIQUID_WMO_Ocean;<br />
case liquid_basic_types_magma:<br />
return LIQUID_WMO_Magma;<br />
case liquid_basic_types_slime:<br />
return LIQUID_WMO_Slime;<br />
}<br />
}<br />
<br />
<br />
if ( mapObj->mohd_data->flag_use_liquid_type_dbc_id )<br />
{<br />
if ( smoGroup->groupLiquid < LIQUID_FIRST_NONBASIC_LIQUID_TYPE )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid - 1);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid;<br />
}<br />
}<br />
else<br />
{<br />
if ( smoGroup->groupLiquid == LIQUID_Green_Lava )<br />
{<br />
this->liquid_type = 0; {{Template:Unverified| // use to_wmo_liquid(SMOLTile->liquid) ? It seems to work alright. }}<br />
}<br />
else<br />
{<br />
int const liquidType (smoGroup->groupLiquid + 1);<br />
if ( smoGroup->groupLiquid < LIQUID_END_BASIC_LIQUIDS )<br />
{<br />
this->liquid_type = to_wmo_liquid (smoGroup->groupLiquid);<br />
}<br />
else<br />
{<br />
this->liquid_type = smoGroup->groupLiquid + 1;<br />
}<br />
assert (!liquidType || !(smoGroup->flags & SMOGroup::LIQUIDSURFACE));<br />
}<br />
}<br />
<br />
== MORI ==<br />
uint16_t triangle_strip_indices[];<br />
<br />
== MORB ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* ignored if !CMap::enableTriangleStrips<br />
* modifies MOBA, therefore has same count.<br />
* size is not checked, but 2 * sizeof(int), even though it is only (int, short).<br />
struct MORB_entry<br />
{<br />
uint32_t start_index;<br />
uint16_t index_count;<br />
uint16_t padding;<br />
}<br />
* overwrites 0xC and 0x10 of MOBA (start, count).<br />
<br />
== MOTA ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* Map Object Tangent Array<br />
<br />
struct MOTA<br />
{<br />
unsigned short first_index[moba_count]; // either -1 or first index of batch.count indices into tangents[]. <br />
// if auto-generated, only has entries for batches with <br />
// material[batch.material].shader == 10 or 14.<br />
{{Template:Type|C4Vector}} tangents[accumulated_num_indices]; // sum (batches[i].count | material[batches[i].material].shader == 10 or 14)<br />
};<br />
<br />
Is auto generated, if there are batches with shaders 10 or 14, but no tangents. (And maybe some additional condition.) See CMapObjGroup::Create().<br />
<br />
== MOBS ==<br />
{{SectionBox/VersionRange|min_expansionlevel=4|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char unk0[10];<br />
short materialIDBig; // Index into MOMT<br />
int field_2; // Divided by 3 upon usage<br />
short field_6; // Divided by 3 upon usage<br />
char unk1[4];<br />
char flagThing; // If & 2 use materialIDBig otherwise use materialIDSmall<br />
char materialIDSmall; // Index into MOMT<br />
} map_object_shadow_batches[];<br />
<br />
== MDAL ==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct<br />
{<br />
{{Template:Type|CArgb}} replacement_for_header_color; // if -1 or not present, take color from header<br />
} mdal;<br />
<br />
==MOPL==<br />
{{SectionBox/VersionRange|min_expansionlevel=6|note={{Unverified|Could have been added earlier}}}}<br />
<br />
* requires MOGP.canCutTerrain<br />
{{Template:Type|C4Plane}} terrain_cutting_planes[<=32];<br />
<br />
==MOPB==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x18];<br />
} map_object_prepass_batches[];<br />
<br />
==MOLS==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
char _1[0x38];<br />
} map_object_spot_lights[];<br />
==MOLP==<br />
{{SectionBox/VersionRange|min_expansionlevel=7|note={{Unverified|Could have been added earlier}}}}<br />
<br />
struct {<br />
uint32_t unk;<br />
CImVector color; <br />
C3Vector pos; //position of light<br />
float intensity; <br />
float attenStart;<br />
float attenEnd;<br />
float unk4; //Only seen zeros here <br />
uint32_t unk5;<br />
uint32_t unk6; //CArgb?<br />
} map_object_point_lights[];<br />
<br />
==MLSS==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t mols_count; // spotlights per set<br />
} map_object_lightset_spotlights[];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSP==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t molp_count; // pointlights per set<br />
} map_object_lightset_pointlights[];<br />
<br />
''note: client again uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSO==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOS2_count;<br />
} mapobject_spotlight_animsets [];<br />
<br />
''note: client uses a record size of 8 (checked with client 29297)''<br />
<br />
==MLSK==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
struct {<br />
uint32_t offset;<br />
uint32_t MOP2_count;<br />
} mapobject_pointlight_animsets[];<br />
<br />
==MOS2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
In binary, not in files<br />
<br />
*'''Unknown struct layout, 108 bytes per struct.'''<br />
<br />
struct {<br />
byte data[108]; // unknown<br />
} map_object_spotlight_anims[];<br />
<br />
==MOP2==<br />
{{SectionBox/VersionRange|min_build=8.1.0.27826|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
<br />
Currently only in file 2143042 as of 8.1.5.28938: world/wmo/zuldazar/orc/8or_pvp_warsongbg_main01.wmo.<br />
<br />
*'''Unknown struct layout, 96 bytes per struct.'''<br />
<br />
struct {<br />
uint32_t _unk00; // index?<br />
CImVector color; <br />
C3Vector pos;<br />
float _unk14[0x8];<br />
char _unk34[0x2C];<br />
} map_object_pointlight_anims[];<br />
<br />
==MPVR==<br />
{{SectionBox/VersionRange|min_build=8.3.0.33775|min_expansionlevel=8|note={{Unverified|Could have been added earlier}}}}<br />
uint16_t mapobject_particulate_volume_refs[];<br />
<br />
==MAVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_ambient_volume_refs[];<br />
<br />
==MBVR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_box_volume_refs[];<br />
<br />
==MNLR==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
uint16_t mapobject_new_light_refs[];<br />
<br />
==MOLM==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
<br />
Lightmaps were the original lighting implementation for WMOs and the default light mode used in the alpha clients. They were replaced by "vertex lighting" in {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.6.0.3592}}.<br />
The alpha clients can switch between light modes using the <tt>mapObjLightMode</tt> console command (CWorld:enables & 0x400).<br />
<br />
This chunk contains information for blitting the [[#MOLD|MOLD]] colour palette. There is one entry for each [[#MOPY_chunk|MOPY]] and is referenced by matching index.<br />
<br />
Exterior lit groups (SMOGroup::EXTERIOR | SMOGroup::EXTERIOR_LIT) are excluded and default to (0,0,0). All other groups have their light colour calculated from the visible SMOPolys using their associated [[#MOLV|MOLV]], [[#MOLM|MOLM]] and [[#MOLD|MOLD]] entries. This colour is then blended with the texture. The client enforces a minimum of 24 for each colour component {{Template:Unverified|and skews the colour based on the dominant RGB component.}}<br />
<br />
struct SMOLightmap<br />
{<br />
char x;<br />
char y;<br />
char width;<br />
char height;<br />
} lightmapList[];<br />
<br />
==MOLD==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=0|max_build=0.5.5.3494|note=Only used in v14}}<br />
This chunk stores a {{Template:Unverified|255x255}} DXT1 compressed colour palette.<br />
struct SMOLightmapTex<br />
{<br />
char texels[32768];<br />
union<br />
{<br />
char inMemPad[4];<br />
CGxTex *gxTexture;<br />
HTEXTURE__ *hTexture;<br />
}; // always inMemPad == 0 in file<br />
} lightmapTexList[];<br />
<br />
==MPB*==<br />
These chunks are barely ever present (the one file known is <tt>StonetalonWheelPlatform.wmo</tt> from alpha). No version of the client ever read them though. They might be an early form of [[PD4]] files, inlined into the WMO and not per root but per group.<br />
<br />
{{Template:Unverified|MPBV and MPBP appear to be <tt>(uint16_t start, uint16_t count)s</tt>. This is reasoned by the values being sequential and totalling the entry count of the next chunk. If this is the case, the structure may actually produce groups of groups of vertices e.g. [https://gist.github.com/barncastle/13f24fbdea8d41980e29734c34063f13 StonetalonWheelPlatform].}}<br />
<br />
===MPBV===<br />
uint16_t mpbv[];<br />
===MPBP===<br />
uint16_t mpbp[];<br />
===MPBI===<br />
uint16_t mpb_indices[]; // {{Template:Unverified|triangle}} vertex indices into into [[#MPBG]]<br />
===MPBG===<br />
{{Template:Type|C3Vector}} mpb_vertices[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=M2/.skin&diff=30249M2/.skin2020-06-06T15:43:08Z<p>Zee: /* Mesh part ID */</p>
<hr />
<div>Okay, there is no ofsViews anymore in [[M2|M2-files]], but we still got nViews at 4 so there has to be a place where this information is stored. This is when the .skin-files come to the light. They got added in WotLK and are in the same folder as the [[M2|M2s]]. They are named like Modelname0x.skin, where Modelname is the same name as the model has and x is a digit from 0 to 3 representing each View / LOD. They are in the same structure as the ofsViews-block has been, just with all offsets now being relative to the .skin-files of course. The vertices are still in the [[M2]] itself since they are the same for all views.<br />
<br />
The files are made up in several blocks. First is a header:<br />
==Header==<br />
struct M2SkinProfile<br />
{<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
uint32_t magic; // 'SKIN'<br />
#endif<br />
M2Array<unsigned short> [[#Indices|vertices ]];<br />
M2Array<unsigned short> [[#Triangles|indices ]];<br />
M2Array<ubyte4> [[#Vertex_properties|bones]];<br />
M2Array<M2SkinSection> [[#Submeshes|submeshes]];<br />
M2Array<M2Batch> [[#Texture_units|batches]];<br />
uint32_t boneCountMax; // WoW takes this and divides it by the number of bones in each submesh, then stores the biggest one.<br />
// Maximum number of bones per drawcall for each view. Related to (old) GPU numbers of registers. <br />
// Values seen : 256, 64, 53, 21<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
M2Array<M2ShadowBatch> [[#shadow_batches|shadow_batches]];<br />
#endif<br />
} header;<br />
<br />
==Indices==<br />
*'''nIndices 16-bit unsigned shorts, specifing vertices from the global vertex list for later use.'''<br />
'''Offset Type Name Description'''<br />
''0x00'' uint16 Vertex The vertex in the [[M2#Vertices|global vertex list]].<br />
<br />
==Triangles==<br />
*'''nTriangles entries of each 3 unsigned shorts. They refer to indices in the list above.'''<br />
'''Offset Type Name Description'''<br />
''0x00'' uint16 Indices[3] Three indices which make up a triangle.<br />
<br />
I believe (empirically tested) that nTriangles is actually not the number of triangles in the list, but the number of vertexes in the triangle list. That is, the actual number of triangles in the list is actually nTriangles / 3. I discovered this when a test application I've been writing attempted to read past the end of the file when not first dividing the number of triangles to read by 3.<br />
<br />
==Vertex properties==<br />
*'''nProperties entries, they are the Bone Indices for the Vertices'''<br />
'''Offset Type Name Description'''<br />
''0x00'' 4*uint8 Properties Bone Indices (Index into BoneLookupTable) <br />
It seems to be an index into actual bones struct, not the lookup table -- Skarn<br />
<br />
==Submeshes==<br />
*'''nSubmeshes entries of 0x30 bytes defining submeshes.'''<br />
struct M2SkinSection<br />
{ <br />
uint16_t skinSectionId; // Mesh part ID, see below.<br />
uint16_t Level; // (level << 16) is added (|ed) to startTriangle and alike to avoid having to increase those fields to uint32s.<br />
uint16_t vertexStart; // Starting vertex number.<br />
uint16_t vertexCount; // Number of vertices.<br />
uint16_t indexStart; // Starting triangle index (that's 3* the number of triangles drawn so far).<br />
uint16_t indexCount; // Number of triangle indices.<br />
uint16_t boneCount; // Number of elements in the [[M2#Bone_Lookup_Table|bone lookup table]]. Max seems to be 256 in {{Template:Sandbox/PrettyVersion|expansionlevel=3}}. Shall be ≠ 0.<br />
uint16_t boneComboIndex; // Starting index in the [[M2#Bone_Lookup_Table|bone lookup table]].<br />
uint16_t boneInfluences; // <= 4<br />
// from <=BC documentation: Highest number of bones needed at one time in this Submesh --Tinyn (wowdev.org) <br />
// In 2.x this is the amount of of bones up the parent-chain affecting the submesh --NaK<br />
// Highest number of bones referenced by a vertex of this submesh. 3.3.5a and suspectedly all other client revisions. -- Skarn<br />
uint16_t centerBoneIndex; <br />
{{Template:Type|C3Vector}} centerPosition; // Average position of all the vertices in the sub mesh.<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}}<br />
{{Template:Type|C3Vector}} sortCenterPosition; // The center of the box when an axis aligned box is built around the vertices in the submesh.<br />
float sortRadius; // Distance of the vertex farthest from CenterBoundingBox.<br />
#endif<br />
} submeshes[];<br />
<br />
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 at [[M2#Bones|ofsBones]].<br />
<br />
In 2.x it seems that StartBones & boneInfluences seem to be the partial bone chain affecting the submesh, boneInfluences being the bone furthest down in hierarchy + (n-1) parent bones up. "n" being the amount given at StartBones. (weirdly i dont know what this means for the submesh_0, seeing how hands and feet/toes are part of it, yet their bones would be part of different subchains, and thus dont receive referencing. Where is the point?) --NaK<br />
<br />
===Mesh part ID===<br />
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 omitted based on this ID.<br />
<br />
The submeshes are sorted into groups. As Blizzard uses multiple integers (3 ?) for masking them, there are 8*i groups possible. Groups are like this for character models. They can be different for other models. Note that ** starts with 01, not 00 (with the exception of entry 0, which is the skin).<br />
0000: Skin<br />
00**: Hair: {1-21: various hairstyles}<br />
01**: Facial1: {1-8: varies} (usually beard, but not always)<br />
02**: Facial2: {1: none (DNE), 2-6: varies} (usually mustache, but not always)<br />
03**: Facial3: {1: none (DNE), 2-11: varies} (usually sideburns, but not always)<br />
04**: Glove: {1-4}<br />
05**: Boots: {1-5}<br />
06**: Tail (Draenei Female)<br />
07**: Ears: {1: none (DNE), 2: ears}<br />
08**: Wristbands / Sleeves: {1: none (DNE), 2: normal, 3: ruffled}<br />
09**: Kneepads / Legcuffs: {1: none (DNE), 2: long, 3: short}<br />
10**: Chest: {1: none (DNE), 2: Unk (exists but purpose unknown - mesh is in all character models lower half of "Torso Lower")}<br />
11**: Pants: {1: regular, 2: short skirt, 4: armored pants}<br />
12**: Tabard: {1: none (DNE), 2: tabard}<br />
13**: Trousers: {1: legs, 2: dress}<br />
14**: DHLoincloth: (probably only present in collection model)<br />
15**: Cloak: {1-10: various cloak styles}<br />
16**: Mechagnome chin (collections only?) {{Template:Sandbox/PrettyVersion|expansionlevel=8|build=8.2.0.30080}}<br />
17**: Eyeglows: {1: none (DNE), 2: racial eyeglow, 3: DK eyeglow}<br />
18**: Belt / bellypack: {1: none (sometimes DNE), 2: bulky belt, 3: Monk Belt}<br />
19**: Bone/Tail (in Legion this group also has Undead bones)<br />
20**: Feet: {1: none, 2: feet}<br />
22**: Torso {1: regular, 2: armored torso}<br />
23**: Hands (attachments) (in addition to character models also exist in DH collections) { 1: hands for blood elf/night elf }<br />
24**: Head attachments (e.g. horns) (only exist in DH/MG collections) {1-x}<br />
25**: Blindfolds (only exist in DH collections) {1-x}<br />
29**: Mechagnome arms/hands (collections only?) {{Template:Sandbox/PrettyVersion|expansionlevel=8|build=8.2.0.30080}}<br />
30**: Mechagnome legs (collections only?) {{Template:Sandbox/PrettyVersion|expansionlevel=8|build=8.2.0.30080}}<br />
31**: Mechagnome feet (collections only?) {{Template:Sandbox/PrettyVersion|expansionlevel=8|build=8.2.0.30080}}<br />
32**: Face {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.0.1.34137}}<br />
33**: Eyes {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.0.1.34137}}<br />
34**: Eyebrows {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.0.1.34137}}<br />
35**: Earrings {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.0.1.34137}}<br />
36**: Necklace {{Template:Sandbox/PrettyVersion|expansionlevel=9|build=9.0.1.34137}}<br />
Some particular geosets (such as 701) are marked as 'DNE' (Does Not Exist). This is to indicate that that particular geoset does not actually exist in any skin files. However, the game will still reference these geosets in the case that it wants nothing in that geoset group to show up.<br />
<br />
You can use this together with [[CreatureDisplayInfo.dbc]].creatureGeosetData for nice effects. Also used in [[ItemDisplayInfo.dbc]].m_geosetGroup[] (see that page for an explanation of how the geoset group fields relate to this).<br />
<br />
===Mesh override===<br />
In wotlk client, the vertex data in M2 file is overridden if CM2Shared->field_4->field4 has 0x8 flag set. The override process includes usage of boneInfluences field.<br />
function CM2Shared.sub837A40() <br />
{<br />
/* Some code is skipped */<br />
<br />
if ( !((_BYTE)field[4]->field_4 & 8) )<br />
{<br />
M2Vertex* override_vertices = SMemNew(sizeof (M2Vertex) * skinFile->indices.count);<br />
<br />
// 2. zero-initialize (but will be overridden with real vertices in 3.)<br />
// 3. Copy data from initial vertex of m2 and override boneIndexes<br />
<br />
for (int meshIndex = 0; meshIndex < skinFile->submeshes.count; ++meshIndex)<br />
{<br />
M2SkinSection* subMesh = skinFile->submeshes.data[meshIndex];<br />
<br />
for (int vertIndex = subMesh->StartVertex; vertIndex < (subMesh->StartVertex + subMesh->vertices.count); ++vertIndex)<br />
{<br />
override_vertices[vertIndex] = m_data->vertices.data[skinFile->indices.data[vertIndex]];<br />
<br />
for (int boneInd = 0; boneInd < subMesh->boneInfluences; ++boneInd)<br />
{<br />
override_vertices[vertIndex].bone_indices[boneInd] =<br />
m_data->bone_lookup_table.data[subMesh->StartBones + skinFile->properties.data[4*vertIndex + boneInd];<br />
}<br />
}<br />
}<br />
<br />
// 4. Override bone lookup table and in m2 file<br />
for (int i = 0; i < m_data->nBoneLookupTable; ++i)<br />
m_data->bone_lookup_table.data[i] = i;<br />
<br />
// 5. Override indicies in skin file<br />
for (int j = 0; j < skinFile->indices.count; ++j)<br />
skinFile->indices.data[j] = j;<br />
<br />
// 6. Override vertex array from m2 with new data<br />
if ( skinFile->indices.count <= m_data->vertices.count )<br />
{<br />
memcpy(m_data->vertices.data, override_vertices, sizeof (M2Vertex) * skinFile->indices.count);<br />
SMemFree (override_vertices);<br />
}<br />
else<br />
{<br />
field_8 |= 8u;<br />
m_data->vertices.data = override_vertices;<br />
}<br />
<br />
m_data->vertices.count = skinFile->indices.count;<br />
}<br />
<br />
// 7. Override batch flags<br />
if ( !((_BYTE)field[4]->field_4 & 8) )<br />
{<br />
for ( int i = 0; i < skinFile->batches.count; i++)<br />
{<br />
if ( skinFile->batches.data[i].op_count > 1u )<br />
skinFile->batches.data[i - skinFile->batches.data[i].layer].flags |= 0x40u;<br />
}<br />
<br />
for ( int i = 0; i < skinFile->batches.count; i++)<br />
{<br />
if ( skinFile->batches.data[i].layer )<br />
{<br />
if ( skinFile->batches.data[i - skinFile->batches.data[i].layer].flags & 0x40 )<br />
skinFile->batches.data[i].flags |= 0x40u;<br />
}<br />
}<br />
}<br />
}<br />
<br />
==Texture units==<br />
*'''nTextureUnits blocks of 0x18 bytes per record.''' (Actually named batches)<br />
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.<br />
<br />
struct M2Batch <br />
{<br />
uint8_t flags; // Usually 16 for static textures, and 0 for animated textures. &0x1: materials invert something; &0x2: transform &0x4: projected texture; &0x10: something batch compatible; &0x20: projected texture?; &0x40: possibly don't multiply transparency by texture weight transparency to get final transparency value(?)<br />
int8_t priorityPlane;<br />
uint16_t shader_id; // See below.<br />
uint16_t skinSectionIndex; // A duplicate entry of a submesh from the list above.<br />
uint16_t geosetIndex; // See below.<br />
uint16_t colorIndex; // A Color out of the [[M2#Submesh_Animations|Colors-Block]] or -1 if none.<br />
uint16_t materialIndex; // The [[M2#Render_flags|renderflags]] used on this texture-unit.<br />
uint16_t materialLayer; // Capped at 7 (see CM2Scene::BeginDraw)<br />
uint16_t textureCount; // 1 to 4. See below. Also seems to be the number of textures to load, starting at the texture lookup in the next field (0x10).<br />
uint16_t textureComboIndex; // Index into [[M2#Texture_lookup_table|Texture lookup table]]<br />
uint16_t textureCoordComboIndex; // Index into the [[M2#Texture_unit_lookup_table|texture unit lookup table]].<br />
uint16_t textureWeightComboIndex; // Index into [[M2#Transparency_lookup_table|transparency lookup table]].<br />
uint16_t textureTransformComboIndex; // Index into [[M2#UV-Animation_lookup_table|uvanimation lookup table]]. <br />
};<br />
===geosetIndex===<br />
-- Rour, what is this? It really doesn't look like a submesh index, I've seen it be !=0 and !=SubmeshIndex, the WoD login screen looks to have some submeshes might have different layouts than normal texunits. The ribbon effects (they're not ribbon emitters) appear to use texture indices that don't match the usual (texture_id + i) pattern. Infact, geosetIndex has what looks like a valid texture index in it. Perhaps a new flag?<br />
<br />
-- FWIW, this offset is never touched by the client. It is possibly some sort of legacy field that is unused nowadays. [[User:Simca|Simca]] ([[User talk:Simca|talk]]) 02:20, 10 April 2016 (CEST)<br />
<br />
===shader_id and textureCount===<br />
Note that this is based on 5.0.1.15464. It may have more values in later versions and less (especially not hull and domain shaders) in lower versions. To get a list of values for your client, look at CM2Shared::GetEffect().<br />
<br />
Based on these two fields, the shaders to load are determined. If shader_id is negative, the (absolute) value of it is used directly to look into s_modelShaderEffect and select from there. If it is positive, selection of the shaders to use will be based on M2Get*ShaderID() functions. Vertex and pixel shaders names are used directly, hull and domain shaders are either prefixed with "Model2_" (tessellation) or "Model2Displ_" (displacement). If neither is enabled, they are not used.<br />
<br />
T1 and T2 seem to point to the Texture Coordinates in the vertex (first or second set), and are listed in order of which texture they apply to.<br />
<br />
Env shaders map texture coords for that texture to a spheremap. This is most often used to give armour and weapon pieces their "shine" by spheremapping the shine texture onto the item.<br />
<br />
''IMPORTANT: The texture and UV animation (and maybe render flags/transparency?) indices listed in the texture units are only the 'base' index. If the textureCount is e.g. 3 and the texunit's uv anim lookup is 2, then the 3 uv animation lookups are 2, 3, and 4. ---[[User:Relaxok|Relaxok]], 12-08-2014<br />
''<br />
<br />
===shader_id in WotLK===<br />
<br />
Note: this entire section only applies to selecting appropriate shaders for WotLK. It may also apply to earlier WoW versions, but it definitely stops applying from Cata and on.<br />
<br />
Unlike shader_id in Cata and later version of WoW, the shader_id field in WotLK M2s is generally 0 in the on-disk skin file. However, this isn't simply because WotLK and earlier don't use a large suite of shaders when rendering. Rather, in WotLK (and potentially TBC and Vanilla), the real value of shader_id is determined at runtime.<br />
<br />
In WotLK, the runtime value of shader_id is computed via two functions which take in to account the blending mode, render flags, op count, texture mapping (T1, T2, Env), and transparency animations. If disassembling with IDA, in Wow.exe Build 12340, the relevant functions are: sub_836980 and sub_837680. They are called in that order from sub_837A40 (the function that loads a skin profile).<br />
<br />
Occasionally, the runtime value for shader_id is computed as 0x8000. In WotLK, 0x8000 translates to no shader. In general, M2Batches with a runtime shader_id of 0x8000 appear to be safe to not render. As an example: The model NorthrendPenguin.m2, in skin 01, has 6 total batches: 3 batches that get assigned shader_id 0x8000 at runtime, and 3 batches that get Combiners_Opaque_Mod2xNA_Alpha. In Cata+, the same model only has 3 batches, all of which are given the shader Combiners_Opaque_Mod2xNA_Alpha.<br />
<br />
[[M2/.skin/WotLK_shader_selection | WotLK runtime shader selection in JavaScript]]<br />
<br />
===Login screens===<br />
The main issue with login screens is that many M2Batch's there point to render flag that tells to turn off write into depth buffer. This combined with the fact that M2Batch'es are not ordered for a proper rendering makes it broken in most renderers at the moment. So basically the z-depth test is off for these screens and it's up to developer to sort the materials.<br />
<br />
Sorting using M2SkinSection.CenterBoundingBox or M2SkinSection.CenterMass doesnt give desired effect for WotLK login screen. <br />
My best guess so far is to calculate bounding boxes upon loading for each M2SkinSection. Then transform each bounding box with ModelViewMatrix to get transformedAABB array([http://dev.theomader.com/transform-bounding-boxes/ How to transform AABB with Mat4]). And next sort materials based on z coordinate, which is distance from mesh to screen in view space. It should also be taken into account if the camera is inside Bounding Box or not. The criteria function looks like this:<br />
var zeroVect = [0, 0, 0]<br />
function test1 (a, b) {<br />
var aabb1_t = transformedAABB[a.submesh_index];<br />
var aabb2_t = transformedAABB[b.submesh_index];<br />
var isInsideAABB1 = isPointInsideAABB(aabb1_t,zeroVect);<br />
var isInsideAABB2 = isPointInsideAABB(aabb2_t,zeroVect);<br />
if (!isInsideAABB1 && isInsideAABB2) {<br />
return 1<br />
} else if (isInsideAABB1 && !isInsideAABB2) {<br />
return -1<br />
}<br />
var result;<br />
if (isInsideAABB1 && isInsideAABB1) {<br />
result = aabb1_t.min.z - aabb2_t.min.z;<br />
} else if (!(isInsideAABB1 && isInsideAABB1)) {<br />
result = aabb2_t.min.z - aabb1_t.min.z;<br />
}<br />
return result;<br />
}<br />
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])<br />
<br />
<br />
===Environment mapping===<br />
<br />
This is the actual formula blizz use for env mapping (vertex and normal are in camera space):<br />
vec2 sphereMap(vec3 vertex, vec3 normal)<br />
{<br />
vec3 normPos = -(normalize(vertex.xyz));<br />
vec3 temp = (normPos - (normal * (2.0 * dot(normPos, normal))));<br />
temp = vec3(temp.x, temp.y, temp.z + 1.0);<br />
<br />
texCoord = ((normalize(temp).xy * 0.5) + vec2(0.5));<br />
}<br />
<br />
<br />
===Vertex shaders===<br />
enum modelVertexShaders<br />
{<br />
VS_Diffuse_T1,<br />
VS_Diffuse_Env,<br />
VS_Diffuse_T1_T2,<br />
VS_Diffuse_T1_Env,<br />
VS_Diffuse_Env_T1,<br />
VS_Diffuse_Env_Env,<br />
VS_Diffuse_T1_Env_T1,<br />
VS_Diffuse_T1_T1,<br />
VS_Diffuse_T1_T1_T1,<br />
VS_Diffuse_EdgeFade_T1,<br />
VS_Diffuse_T2,<br />
VS_Diffuse_T1_Env_T2,<br />
VS_Diffuse_EdgeFade_T1_T2,<br />
VS_Diffuse_T1_T1_T1_T2,<br />
VS_Diffuse_EdgeFade_Env,<br />
VS_Diffuse_T1_T2_T1,<br />
};<br />
const char* s_modelVertexShaders[16] =<br />
{<br />
"Diffuse_T1",<br />
"Diffuse_Env",<br />
"Diffuse_T1_T2",<br />
"Diffuse_T1_Env",<br />
"Diffuse_Env_T1",<br />
"Diffuse_Env_Env",<br />
"Diffuse_T1_Env_T1",<br />
"Diffuse_T1_T1",<br />
"Diffuse_T1_T1_T1",<br />
"Diffuse_EdgeFade_T1",<br />
"Diffuse_T2",<br />
"Diffuse_T1_Env_T2",<br />
"Diffuse_EdgeFade_T1_T2",<br />
"Diffuse_T1_T1_T1_T2",<br />
"Diffuse_EdgeFade_Env",<br />
"Diffuse_T1_T2_T1",<br />
};<br />
<br />
===Vertex shaders (8.0.1)===<br />
enum modelVertexShaders<br />
{<br />
VS_Diffuse_T1,<br />
VS_Diffuse_Env,<br />
VS_Diffuse_T1_T2,<br />
VS_Diffuse_T1_Env,<br />
VS_Diffuse_Env_T1,<br />
VS_Diffuse_Env_Env,<br />
VS_Diffuse_T1_Env_T1,<br />
VS_Diffuse_T1_T1,<br />
VS_Diffuse_T1_T1_T1,<br />
VS_Diffuse_EdgeFade_T1,<br />
VS_Diffuse_T2,<br />
VS_Diffuse_T1_Env_T2,<br />
VS_Diffuse_EdgeFade_T1_T2,<br />
VS_Diffuse_EdgeFade_Env,<br />
VS_Diffuse_T1_T2_T1,<br />
VS_Diffuse_T1_T2_T3,<br />
VS_Color_T1_T2_T3,<br />
VS_BW_Diffuse_T1,<br />
VS_BW_Diffuse_T1_T2,<br />
};<br />
<br />
===Pixel shaders===<br />
enum modelPixelShaders<br />
{<br />
PS_Combiners_Opaque,<br />
PS_Combiners_Mod,<br />
PS_Combiners_Opaque_Mod,<br />
PS_Combiners_Opaque_Mod2x,<br />
PS_Combiners_Opaque_Mod2xNA,<br />
PS_Combiners_Opaque_Opaque,<br />
PS_Combiners_Mod_Mod,<br />
PS_Combiners_Mod_Mod2x,<br />
PS_Combiners_Mod_Add,<br />
PS_Combiners_Mod_Mod2xNA,<br />
PS_Combiners_Mod_AddNA,<br />
PS_Combiners_Mod_Opaque,<br />
PS_Combiners_Opaque_Mod2xNA_Alpha,<br />
PS_Combiners_Opaque_AddAlpha,<br />
PS_Combiners_Opaque_AddAlpha_Alpha,<br />
PS_Combiners_Opaque_Mod2xNA_Alpha_Add,<br />
PS_Combiners_Mod_AddAlpha,<br />
PS_Combiners_Mod_AddAlpha_Alpha,<br />
PS_Combiners_Opaque_Alpha_Alpha,<br />
PS_Combiners_Opaque_Mod2xNA_Alpha_3s,<br />
PS_Combiners_Opaque_AddAlpha_Wgt,<br />
PS_Combiners_Mod_Add_Alpha,<br />
PS_Combiners_Opaque_ModNA_Alpha,<br />
PS_Combiners_Mod_AddAlpha_Wgt,<br />
PS_Combiners_Opaque_Mod_Add_Wgt,<br />
PS_Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha,<br />
PS_Combiners_Mod_Dual_Crossfade,<br />
PS_Combiners_Opaque_Mod2xNA_Alpha_Alpha,<br />
PS_Combiners_Mod_Masked_Dual_Crossfade,<br />
PS_Combiners_Opaque_Alpha,<br />
PS_Guild,<br />
PS_Guild_NoBorder,<br />
PS_Guild_Opaque,<br />
PS_Combiners_Mod_Depth,<br />
PS_Illum,<br />
PS_Combiners_Mod_Mod_Mod_Const,<br />
};<br />
const char* s_modelPixelShaders[36] =<br />
{<br />
"Combiners_Opaque",<br />
"Combiners_Mod",<br />
"Combiners_Opaque_Mod",<br />
"Combiners_Opaque_Mod2x",<br />
"Combiners_Opaque_Mod2xNA",<br />
"Combiners_Opaque_Opaque",<br />
"Combiners_Mod_Mod",<br />
"Combiners_Mod_Mod2x",<br />
"Combiners_Mod_Add",<br />
"Combiners_Mod_Mod2xNA",<br />
"Combiners_Mod_AddNA",<br />
"Combiners_Mod_Opaque",<br />
"Combiners_Opaque_Mod2xNA_Alpha",<br />
"Combiners_Opaque_AddAlpha",<br />
"Combiners_Opaque_AddAlpha_Alpha",<br />
"Combiners_Opaque_Mod2xNA_Alpha_Add",<br />
"Combiners_Mod_AddAlpha",<br />
"Combiners_Mod_AddAlpha_Alpha",<br />
"Combiners_Opaque_Alpha_Alpha",<br />
"Combiners_Opaque_Mod2xNA_Alpha_3s",<br />
"Combiners_Opaque_AddAlpha_Wgt",<br />
"Combiners_Mod_Add_Alpha",<br />
"Combiners_Opaque_ModNA_Alpha",<br />
"Combiners_Mod_AddAlpha_Wgt",<br />
"Combiners_Opaque_Mod_Add_Wgt",<br />
"Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha",<br />
"Combiners_Mod_Dual_Crossfade",<br />
"Combiners_Opaque_Mod2xNA_Alpha_Alpha",<br />
"Combiners_Mod_Masked_Dual_Crossfade",<br />
"Combiners_Opaque_Alpha",<br />
"Guild",<br />
"Guild_NoBorder",<br />
"Guild_Opaque",<br />
"Combiners_Mod_Depth",<br />
"Illum",<br />
"Combiners_Mod_Mod_Mod_Const",<br />
};<br />
<br />
===Hull shaders===<br />
enum modelHullShaders<br />
{<br />
HS_T1,<br />
HS_T1_T2,<br />
HS_T1_T2_T3,<br />
HS_T1_T2_T3_T4,<br />
};<br />
const char* s_modelHullShaders[16] =<br />
{<br />
"T1",<br />
"T1_T2",<br />
"T1_T2_T3",<br />
"T1_T2_T3_T4",<br />
};<br />
<br />
===Domain shaders===<br />
enum modelDomainShaders<br />
{<br />
DS_T1,<br />
DS_T1_T2,<br />
DS_T1_T2_T3,<br />
DS_T1_T2_T3_T4,<br />
};<br />
const char* s_modelDomainShaders[16] =<br />
{<br />
"T1",<br />
"T1_T2",<br />
"T1_T2_T3",<br />
"T1_T2_T3_T4",<br />
};<br />
<br />
<br />
===Shader table===<br />
struct<br />
{<br />
unsigned int pixel;<br />
unsigned int vertex;<br />
unsigned int hull;<br />
unsigned int domain;<br />
unsigned int ff_colorOp;<br />
unsigned int ff_alphaOp;<br />
} s_modelShaderEffect[NUM_M2SHADERS] = <br />
{ {PS_Combiners_Opaque_Mod2xNA_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_AddAlpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_AddAlpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_Mod2xNA_Alpha_Add, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3, 0, 3},<br />
{PS_Combiners_Mod_AddAlpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 0},<br />
{PS_Combiners_Opaque_AddAlpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Mod_AddAlpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 0},<br />
{PS_Combiners_Mod_AddAlpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 0},<br />
{PS_Combiners_Opaque_Alpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_Mod2xNA_Alpha_3s, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3, 0, 3},<br />
{PS_Combiners_Opaque_AddAlpha_Wgt, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Mod_Add_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 0},<br />
{PS_Combiners_Opaque_ModNA_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Mod_AddAlpha_Wgt, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Mod_AddAlpha_Wgt, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_AddAlpha_Wgt, VS_Diffuse_T1_T2, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_Mod_Add_Wgt, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3, 0, 3},<br />
{PS_Combiners_Mod_Dual_Crossfade, VS_Diffuse_T1_T1_T1, HS_T1_T2_T3, DS_T1_T2_T3, 0, 0},<br />
{PS_Combiners_Mod_Depth, VS_Diffuse_EdgeFade_T1, HS_T1, DS_T1, 0, 0},<br />
{PS_Combiners_Mod_AddAlpha_Alpha, VS_Diffuse_T1_Env_T2, HS_T1_T2_T3, DS_T1_T2_T3, 0, 3},<br />
{PS_Combiners_Mod_Mod, VS_Diffuse_EdgeFade_T1_T2, HS_T1_T2, DS_T1_T2, 0, 0},<br />
{PS_Combiners_Mod_Masked_Dual_Crossfade, VS_Diffuse_T1_T1_T1_T2, HS_T1_T2_T3_T4, DS_T1_T2_T3_T4, 0, 0},<br />
{PS_Combiners_Opaque_Alpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 3},<br />
{PS_Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha, VS_Diffuse_T1_Env_T2, HS_T1_T2_T3, DS_T1_T2_T3, 0, 3},<br />
{PS_Combiners_Mod_Depth, VS_Diffuse_EdgeFade_Env, HS_T1, DS_T1, 0, 0},<br />
{PS_Guild, VS_Diffuse_T1_T2_T1, HS_T1_T2_T3, DS_T1_T2, 0, 0},<br />
{PS_Guild_NoBorder, VS_Diffuse_T1_T2, HS_T1_T2, DS_T1_T2_T3, 0, 0},<br />
{PS_Guild_Opaque, VS_Diffuse_T1_T2_T1, HS_T1_T2_T3, DS_T1_T2, 0, 0},<br />
{PS_Illum, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2, 0, 0},<br />
};<br />
<br />
===Shader table (8.0.1)===<br />
struct<br />
{<br />
unsigned int pixel;<br />
unsigned int vertex;<br />
unsigned int hull;<br />
unsigned int domain;<br />
} s_modelShaderEffect[NUM_M2SHADERS] = <br />
{ { PS_Combiners_Opaque_Mod2xNA_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_AddAlpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_AddAlpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Mod2xNA_Alpha_Add, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Mod_AddAlpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_AddAlpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_AddAlpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_AddAlpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Alpha_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Mod2xNA_Alpha_3s, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Opaque_AddAlpha_Wgt, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_Add_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_ModNA_Alpha, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_AddAlpha_Wgt, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_AddAlpha_Wgt, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_AddAlpha_Wgt, VS_Diffuse_T1_T2, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Mod_Add_Wgt, VS_Diffuse_T1_Env, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha, VS_Diffuse_T1_Env_T1, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Mod_Dual_Crossfade, VS_Diffuse_T1, HS_T1, DS_T1 },<br />
{ PS_Combiners_Mod_Depth, VS_Diffuse_EdgeFade_T1, HS_T1, DS_T1 },<br />
{ PS_Combiners_Opaque_Mod2xNA_Alpha_Alpha, VS_Diffuse_T1_Env_T2, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Mod_Mod, VS_Diffuse_EdgeFade_T1_T2, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_Masked_Dual_Crossfade, VS_Diffuse_T1_T2, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Alpha, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Opaque_Mod2xNA_Alpha_UnshAlpha, VS_Diffuse_T1_Env_T2, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Mod_Depth, VS_Diffuse_EdgeFade_Env, HS_T1, DS_T1 },<br />
{ PS_Guild, VS_Diffuse_T1_T2_T1, HS_T1_T2_T3, DS_T1_T2 },<br />
{ PS_Guild_NoBorder, VS_Diffuse_T1_T2, HS_T1_T2, DS_T1_T2_T3 },<br />
{ PS_Guild_Opaque, VS_Diffuse_T1_T2_T1, HS_T1_T2_T3, DS_T1_T2 },<br />
{ PS_Illum, VS_Diffuse_T1_T1, HS_T1_T2, DS_T1_T2 },<br />
{ PS_Combiners_Mod_Mod_Mod_Const, VS_Diffuse_T1_T2_T3, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Mod_Mod_Mod_Const, VS_Color_T1_T2_T3, HS_T1_T2_T3, DS_T1_T2_T3 },<br />
{ PS_Combiners_Opaque, VS_Diffuse_T1, HS_T1, DS_T1 },<br />
{ PS_Combiners_Mod_Mod2x, VS_Diffuse_EdgeFade_T1_T2, HS_T1_T2, DS_T1_T2 },<br />
};<br />
<br />
unsigned int M2GetPixelShaderID (unsigned int op_count, unsigned short shader_id)<br />
{<br />
if (shader_id & 0x8000)<br />
{<br />
unsigned short const shaderID (shader_id & (~0x8000));<br />
assert (shaderID < NUM_M2SHADERS);<br />
return s_modelShaderEffect (shaderID).pixel;<br />
}<br />
else<br />
{<br />
if (op_count == 1)<br />
{<br />
return shader_id & 0x70 ? PS_Combiners_Mod : PS_Combiners_Opaque;<br />
}<br />
else<br />
{<br />
const unsigned int lower (shader_id & 7);<br />
if (shader_id & 0x70)<br />
{<br />
return lower == 0 ? PS_Combiners_Mod_Opaque<br />
: lower == 3 ? PS_Combiners_Mod_Add<br />
: lower == 4 ? PS_Combiners_Mod_Mod2x<br />
: lower == 6 ? PS_Combiners_Mod_Mod2xNA<br />
: lower == 7 ? PS_Combiners_Mod_AddNA<br />
: PS_Combiners_Mod_Mod;<br />
}<br />
else<br />
{<br />
return lower == 0 ? PS_Combiners_Opaque_Opaque<br />
: lower == 3 ? PS_Combiners_Opaque_AddAlpha<br />
: lower == 4 ? PS_Combiners_Opaque_Mod2x<br />
: lower == 6 ? PS_Combiners_Opaque_Mod2xNA<br />
: lower == 7 ? PS_Combiners_Opaque_AddAlpha<br />
: PS_Combiners_Opaque_Mod;<br />
}<br />
}<br />
}<br />
}<br />
<br />
unsigned int M2GetVertexShaderID (unsigned int op_count, unsigned short shader_id)<br />
{<br />
if (shader_id & 0x8000)<br />
{<br />
unsigned short const shaderID (shader_id & (~0x8000));<br />
assert (shaderID < NUM_M2SHADERS);<br />
return s_modelShaderEffect (shaderID).vertex;<br />
}<br />
else<br />
{<br />
if (op_count == 1)<br />
{<br />
return shader_id & 0x80 ? VS_Diffuse_Env<br />
: shader_id & 0x4000 ? VS_Diffuse_T2<br />
: VS_Diffuse_T1;<br />
}<br />
else<br />
{<br />
if (shader_id & 0x80)<br />
{<br />
return shader_id & 0x8 ? VS_Diffuse_Env_Env<br />
: VS_Diffuse_Env_T1;<br />
}<br />
else<br />
{<br />
return shader_id & 0x8 ? VS_Diffuse_T1_Env<br />
: shader_id & 0x4000 ? VS_Diffuse_T1_T2<br />
: VS_Diffuse_T1_T1;<br />
}<br />
}<br />
}<br />
}<br />
<br />
unsigned int M2GetHullShaderID (unsigned int op_count, unsigned short shader_id)<br />
{<br />
if (shader_id & 0x8000)<br />
{<br />
unsigned short const shaderID (shader_id & (~0x8000));<br />
assert (shaderID < NUM_M2SHADERS);<br />
return s_modelShaderEffect (shaderID).hull;<br />
}<br />
else<br />
{<br />
return op_count == 1 ? HS_T1 : HS_T1_T2;<br />
}<br />
}<br />
<br />
unsigned int M2GetDomainShaderID (unsigned int op_count, unsigned short shader_id)<br />
{<br />
if (shader_id & 0x8000)<br />
{<br />
unsigned short const shaderID (shader_id & (~0x8000));<br />
assert (shaderID < NUM_M2SHADERS);<br />
return s_modelShaderEffect (shaderID).domain;<br />
}<br />
else<br />
{<br />
return op_count == 1 ? DS_T1 : DS_T1_T2;<br />
}<br />
}<br />
<br />
void M2GetFixedFunctionFallback (unsigned short shader_id, EGxTexOp* colorOp, EGxTexOp* alphaOp)<br />
{<br />
if (shader_id & 0x8000)<br />
{<br />
unsigned short const shaderID (shader_id & (~0x8000));<br />
assert (shaderID < NUM_M2SHADERS);<br />
*colorOp = s_modelShaderEffect (shaderID).ff_colorOp;<br />
*alphaOp = s_modelShaderEffect (shaderID).ff_alphaOp;<br />
}<br />
else<br />
{<br />
*colorOp = 0;<br />
*alphaOp = shader_id & 0x70 ? 0 : 3;<br />
}<br />
}<br />
<br />
void M2GetCombinerOps (unsigned short shader_id, unsigned int op_count, EGxTexOp* colorOp, EGxTexOp* alphaOp)<br />
{<br />
int helper[2] = {(shader_id >> 4) & 7, shader_id & 7};<br />
for (int i = 0; i < op_count; ++i)<br />
{<br />
//! \todo Add enum.<br />
static const unsigned int alphaOpTable[] = {3, 0, 3, 2, 1, 3, 3, 3};<br />
static const unsigned int colorOpTable[] = {0, 0, 4, 2, 1, 5, 1, 2};<br />
*colorOp[i] = colorOpTable[helper[i]];<br />
*alphaOp[i] = alphaOpTable[helper[i]];<br />
}<br />
}<br />
<br />
const char* M2GetPixelShaderName (unsigned int op_count, unsigned short shader_id)<br />
{<br />
unsigned int pixelShaderID (M2GetPixelShaderID (op_count, shader_id));<br />
array_size_check (pixelShaderID, s_modelPixelShaders);<br />
return s_modelPixelShaders[pixelShaderID];<br />
}<br />
const char* M2GetVertexShaderName (unsigned int op_count, unsigned short shader_id)<br />
{<br />
unsigned int vertexShaderID (M2GetVertexShaderID (op_count, shader_id));<br />
array_size_check (vertexShaderID, s_modelVertexShaders);<br />
return s_modelVertexShaders[vertexShaderID];<br />
}<br />
const char* M2GetHullShaderName (unsigned int op_count, unsigned short shader_id)<br />
{<br />
unsigned int hullShaderID (M2GetHullShaderID (op_count, shader_id));<br />
array_size_check (hullShaderID, s_modelHullShaders);<br />
return s_modelHullShaders[hullShaderID];<br />
}<br />
const char* M2GetDomainShaderName (unsigned int op_count, unsigned short shader_id)<br />
{<br />
unsigned int domainShaderID (M2GetDomainShaderID (op_count, shader_id));<br />
array_size_check (domainShaderID, s_modelDomainShaders);<br />
return s_modelDomainShaders[domainShaderID];<br />
}<br />
<br />
CShaderEffect* CM2Shared::GetEffect (M2Batch *batch)<br />
{<br />
assert (batch);<br />
<br />
// get names for shaders<br />
<br />
const char* vertex_shader_name (M2GetVertexShaderName (batch->op_count, batch->shader_id));<br />
const char* pixel_shader_name (M2GetPixelShaderName (batch->op_count, batch->shader_id));<br />
<br />
char hull_shader_name_prefixed[0x100];<br />
hull_shader_name_prefixed[0] = 0;<br />
char domain_shader_name_prefixed[0x100];<br />
domain_shader_name_prefixed[0] = 0;<br />
<br />
if (CShaderEffect::TesselationEnabled())<br />
{<br />
SStrPrintf (hull_shader_name_prefixed, 0x100u, "Model2_%s", M2GetHullShaderName(batch->op_count, batch->shader_id));<br />
SStrPrintf (domain_shader_name_prefixed, 0x100u, "Model2_%s", M2GetDomainShaderName(batch->op_count, batch->shader_id));<br />
}<br />
else if (CShaderEffect::DisplacementEnabled())<br />
{<br />
SStrPrintf (hull_shader_name_prefixed, 0x100u, "Model2Displ_%s", M2GetHullShaderName(batch->op_count, batch->shader_id));<br />
SStrPrintf (domain_shader_name_prefixed, 0x100u, "Model2Displ_%s", M2GetDomainShaderName(batch->op_count, batch->shader_id));<br />
}<br />
<br />
// assemble effect name and look in cache<br />
<br />
char effect_name[0x100];<br />
if (batch->shader_id & 0x8000)<br />
{<br />
SStrPrintf (effect_name, 0x100u, "M2Effect %d", batch->shader_id & (~0x8000));<br />
}<br />
else<br />
{<br />
strcpy (effect_name, vertex_shader_name);<br />
strcat (effect_name, pixel_shader_name);<br />
}<br />
<br />
CShaderEffect* effect (CShaderEffectManager::GetEffect (effect_name));<br />
if (effect)<br />
{<br />
effect->AddRef();<br />
return effect;<br />
}<br />
<br />
// create shader and initialize<br />
<br />
effect = CShaderEffectManager::CreateEffect (effect_name);<br />
effect->InitEffect (vertex_shader_name, hull_shader_name_prefixed, domain_shader_name_prefixed, pixel_shader_name);<br />
<br />
if (batch->shader_id < 0)<br />
{<br />
EGxTexOp colorOp;<br />
EGxTexOp alphaOp;<br />
M2GetFixedFunctionFallback (batch->shader_id, &colorOp, &alphaOp);<br />
effect->InitFixedFuncPass (&colorOp, &alphaOp, 1);<br />
}<br />
else<br />
{<br />
EGxTexOp colorOps[2];<br />
EGxTexOp alphaOps[2];<br />
M2GetCombinerOps (batch->shader_id, batch->op_count, colorOps, alphaOps);<br />
effect->InitFixedFuncPass (colorOps, alphaOps, batch->op_count);<br />
}<br />
<br />
assert (effect);<br />
return effect;<br />
}<br />
<br />
==shadow batches==<br />
Apparently based on M2Batch (texture unit).<br />
<br />
struct M2ShadowBatch <br />
{<br />
uint8_t flags; // if auto-generated: M2Batch.flags & 0xFF<br />
uint8_t flags2; // if auto-generated: (renderFlag[i].flags & 0x04 ? 0x01 : 0x00)<br />
// | (!renderFlag[i].blendingmode ? 0x02 : 0x00)<br />
// | (renderFlag[i].flags & 0x80 ? 0x04 : 0x00)<br />
// | (renderFlag[i].flags & 0x400 ? 0x06 : 0x00)<br />
uint16_t _unknown1;<br />
uint16_t submesh_id;<br />
uint16_t texture_id; // already looked-up<br />
uint16_t color_id;<br />
uint16_t transparency_id; // already looked-up<br />
}; <br />
<br />
Generated on the fly, if !(batches[i].flags & 4) && !batches[i].texunit && !(renderflags[batches[i].renderFlag].flags & 0x40) && (renderflags[batches[i].renderFlag].blendingmode < 2u || renderflags[batches[i].renderFlag].flags & 0x80)<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=M2&diff=29604M22020-04-14T02:06:35Z<p>Zee: /* PFDC */</p>
<hr />
<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. <br />
<br />
Models are used for doodads (decoration objects), players, monsters and really everything in the game except for [[ADT|Terrain]] and [[WMO]]s.<br />
<br />
==Header==<br />
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.<br />
<br />
struct <br />
{<br />
// note: Offsets are for {{Template:Sandbox/VersionRange|min_expansionlevel=3}}!<br />
/*0x000*/ uint32_t magic; // "MD20". [[M2#Legion|Legion]] uses a chunked file format starting with MD21.<br />
/*0x004*/ uint32_t [[#Versions|version]];<br />
/*0x008*/ {{Template:Type/M2Array|char}} name; // should be globally unique, used to reload by name in internal clients<br />
<br />
/*0x010*/ struct<br />
{<br />
uint32_t flag_tilt_x : 1;<br />
uint32_t flag_tilt_y : 1;<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}} // TODO: verify version<br />
uint32_t flag_use_texture_combiner_combos : 1; // add textureCombinerCombos array to end of data<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=5}}<br />
uint32_t flag_load_phys_data : 1;<br />
uint32_t : 1;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=6}}<br />
uint32_t flag_unk_0x80 : 1; // with this flag unset, demon hunter tattoos stop glowing<br />
// since {{Template:Unverified|{{Template:Sandbox/PrettyVersion|expansionlevel=4|build=4.0.1.12911}}}} {{Template:Unverified|every}} model now has this flag<br />
uint32_t flag_camera_related : 1; // TODO: verify version<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=7}} // TODO: verify version, these are just added based on where I first saw them -- schlumpf.<br />
uint32_t flag_new_particle_record : 1; // In CATA: new version of ParticleEmitters. By default, length of M2ParticleOld is 476. <br />
// But if 0x200 is set or if version is bigger than 271, length of M2ParticleOld is 492.<br />
uint32_t flag_unk_0x400 : 1;<br />
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<br />
uint32_t flag_unk_0x1000 : 1;<br />
uint32_t flag_unk_0x2000 : 1; // seen in various legion models<br />
uint32_t flag_unk_0x4000 : 1;<br />
uint32_t flag_unk_0x8000 : 1; // seen in UI_MainMenu_Legion<br />
uint32_t flag_unk_0x10000 : 1;<br />
uint32_t flag_unk_0x20000 : 1;<br />
uint32_t flag_unk_0x40000 : 1;<br />
uint32_t flag_unk_0x80000 : 1;<br />
uint32_t flag_unk_0x100000 : 1;<br />
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<br />
#endif<br />
#endif<br />
#endif<br />
#endif<br />
} global_flags;<br />
<br />
/*0x014*/ {{Template:Type/M2Array|M2Loop|section=Global_sequences}} global_loops; // Timestamps used in [[#Global_Sequences_2|global looping animations]].<br />
/*0x01C*/ {{Template:Type/M2Array|M2Sequence|section=Animation_sequences}} sequences; // Information about the animations in the model.<br />
/*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.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2SequenceFallback|section=Playable_Animation_Lookup}} playable_animation_lookup;<br />
#endif<br />
/*0x02C*/ {{Template:Type/M2Array|M2CompBone|section=Bones}} bones; // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones ({{Template:Sandbox/PrettyVersion|expansionlevel=3}})<br />
/*0x034*/ {{Template:Type/M2Array|uint16_t|section=Key-Bone_Lookup}} boneIndicesById; //Lookup table for key skeletal bones. (alt. name: key_bone_lookup)<br />
/*0x03C*/ {{Template:Type/M2Array|M2Vertex|section=Vertices}} vertices;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2SkinProfile|section=Views_.28LOD.29}} skin_profiles;<br />
#else<br />
/*0x044*/ uint32_t num_skin_profiles; // [[#Views_.28LOD.29|Views (LOD)]] are now in [[M2/.skin|.skins]].<br />
#endif<br />
<br />
/*0x048*/ {{Template:Type/M2Array|M2Color|section=Colors}} colors; // Color and alpha animations definitions.<br />
/*0x050*/ {{Template:Type/M2Array|M2Texture|section=Textures}} textures;<br />
/*0x058*/ {{Template:Type/M2Array|M2TextureWeight|section=Transparency}} texture_weights; // Transparency of textures.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
{{Template:Type/M2Array|M2TextureFlipbook}} texture_flipbooks; // never seen in file, 4 uint32_t fields. (M2Track<ushort>)<br />
#endif<br />
/*0x060*/ {{Template:Type/M2Array|M2TextureTransform|section=Texture_Transforms}} texture_transforms;<br />
/*0x068*/ {{Template:Type/M2Array|uint16_t|section=Replacable_texture_lookup}} textureIndicesById; // (alt. name: replacable_texture_lookup)<br />
/*0x070*/ {{Template:Type/M2Array|M2Material|section=Render_flags}} materials; // Blending modes / render flags.<br />
/*0x078*/ {{Template:Type/M2Array|uint16_t|section=Bone_Lookup_Table}} boneCombos; // (alt. name: bone_lookup_table)<br />
/*0x080*/ {{Template:Type/M2Array|uint16_t|section=Texture_lookup_table}} textureCombos; // (alt. name: texture_lookup_table)<br />
/*0x088*/ {{Template:Type/M2Array|uint16_t|section=Texture_unit_lookup_table}} textureTransformBoneMap; // (alt. name: tex_unit_lookup_table)<br />
/*0x090*/ {{Template:Type/M2Array|uint16_t|section=Transparency_lookup_table}} textureWeightCombos; // (alt. name: transparency_lookup_table)<br />
/*0x098*/ {{Template:Type/M2Array|uint16_t|section=Texture_Transforms_lookup_table}} textureTransformCombos; // (alt. name: texture_transforms_lookup_table)<br />
<br />
/*0x0A0*/ {{Template:Type|CAaBox}} bounding_box; // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height<br />
/*0x0B8*/ float bounding_sphere_radius; // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …)<br />
/*0x0BC*/ {{Template:Type|CAaBox}} collision_box;<br />
/*0x0D4*/ float collision_sphere_radius;<br />
<br />
/*0x0D8*/ {{Template:Type/M2Array|uint16_t|section=Triangles}} collisionIndices; // (alt. name: collision_triangles)<br />
/*0x0E0*/ {{Template:Type/M2Array|C3Vector|section=Vertices_2}} collisionPositions; // (alt. name: collision_vertices)<br />
/*0x0E8*/ {{Template:Type/M2Array|C3Vector|section=Normals}} collisionFaceNormals; // (alt. name: collision_normals) <br />
/*0x0F0*/ {{Template:Type/M2Array|M2Attachment|section=Attachments}} attachments; // position of equipped weapons or effects<br />
/*0x0F8*/ {{Template:Type/M2Array|uint16_t|section=Attachment_Lookup}} attachmentIndicesById; // (alt. name: attachment_lookup_table)<br />
/*0x100*/ {{Template:Type/M2Array|M2Event|section=Events}} events; // Used for playing sounds when dying and a lot else.<br />
/*0x108*/ {{Template:Type/M2Array|M2Light|section=Lights}} lights; // Lights are mainly used in loginscreens but in wands and some doodads too.<br />
/*0x110*/ {{Template:Type/M2Array|M2Camera|section=Cameras}} cameras; // The cameras are present in most models for having a model in the character tab. <br />
/*0x118*/ {{Template:Type/M2Array|uint16_t|section=Camera_lookup_table}} cameraIndicesById; // (alt. name: camera_lookup_table)<br />
/*0x120*/ {{Template:Type/M2Array|M2Ribbon|section=Ribbon_emitters}} ribbon_emitters; // Things swirling around. See the CoT-entrance for light-trails.<br />
/*0x128*/ {{Template:Type/M2Array|M2Particle|section=Particle_emitters}} particle_emitters;<br />
<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=2}} // TODO: verify version<br />
if (flag_use_texture_combiner_combos)<br />
{<br />
/*0x130*/ {{Template:Type/M2Array|uint16_t|section=Blend_mode_overrides}} textureCombinerCombos; // When set, textures blending is overriden by the associated array.<br />
}<br />
#endif<br />
} header;<br />
<br />
==Types==<br />
struct M2Bounds {<br />
CAaBox extent;<br />
float radius;<br />
};<br />
template<typename T><br />
struct M2Array {<br />
uint32_t size;<br />
uint32_t offset; // pointer to T, relative to begin of m2 data block (i.e. MD21 chunk content or begin of file)<br />
};<br />
struct M2TrackBase {<br />
uint16_t trackType;<br />
uint16_t loopIndex;<br />
M2Array<M2SequenceTimes> sequenceTimes;<br />
};<br />
template<typename T> <br />
struct M2PartTrack {<br />
M2Array<fixed16> times;<br />
M2Array<T> values;<br />
};<br />
template<typename T> <br />
struct M2SplineKey {<br />
T value;<br />
T inTan;<br />
T outTan;<br />
};<br />
struct M2Range {<br />
uint32_t minimum;<br />
uint32_t maximum;<br />
};<br />
<br />
==Versions==<br />
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.<br />
<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Version !! width="190" | Extension<br />
|-<br />
| 272-274 || Legion, Battle for Azeroth<br />
|-<br />
| 272 || Mists of Pandaria, Warlords of Draenor<br />
|-<br />
| 265-272 || Cataclysm<br />
|-<br />
| 264 || Wrath of the Lich King<br />
|-<br />
| 260-263 || The Burning Crusade<br />
|-<br />
| 256-257 || Classic<br />
|-<br />
| 256 || Pre-Release<br />
|}<br />
<br />
==Chunks==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}<br />
<br />
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. <br />
<br />
'''NOTE''': Unlike all other chunked formats in WoW, chunk names in M2 are '''NOT''' reversed. Example: AFID == AFID in file.<br />
<br />
===MD21===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740}}<br />
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''.<br />
M2Data pre_legion_style_data;<br />
<br />
===PFID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20740|note=This used to be filename based, using <tt>${basename}.phys</tt>}}<br />
uint32_t phys_file_id;<br />
<br />
===SFID===<br />
{{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>}}<br />
uint32_t skinFileDataIDs[header.nViews];<br />
uint32_t lod_skinFileDataIDs[lodBands /* 2? */];<br />
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.<br />
<br />
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.<br />
<br />
===AFID===<br />
{{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>}}<br />
struct<br />
{<br />
uint16_t anim_id;<br />
uint16_t sub_anim_id;<br />
uint32_t file_id; // might be 0 for "none" (so this is probably not sparse, even if it could be)<br />
} anim_file_ids[];<br />
<br />
===BFID===<br />
{{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>}}<br />
uint32_t boneFileDataIDs[];<br />
<br />
===TXAC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=It is unknown what this replaced. {{Unverified|Exact build unknown, not the first one though}}}}<br />
struct {<br />
char unk[2]; // likely used in CM2SceneRender::SetupTextureTransforms and uploaded to the shader directly. 0 otherwise.<br />
} texture_ac[m2data.header.materials.count + m2data.header.particles.count];<br />
<br />
<br />
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<br />
<br />
===EXPT===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown, not the first one though}}}}<br />
struct {<br />
_DWORD zSource;<br />
_DWORD unk1;<br />
_DWORD unk2;<br />
} extended_particle[m2data.header.particles.count];<br />
<br />
Probably outdated chunk after introduction of EXP2 chunk. If EXP2 doesnt exist, client tries to reconstruct it with data from EXPT chunk.<br />
<br />
===EXP2===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to partially replace M2ParticleOld's variables. {{Unverified|Exact build unknown}}}}<br />
<br />
<source><br />
struct M2ExtendedParticle<br />
{<br />
_DWORD zSource;<br />
_DWORD unk1;<br />
_DWORD unk2;<br />
M2PartTrack<fixed16> unk3;<br />
};<br />
<br />
struct M2InitExtendedParticleArray<br />
{<br />
M2Array<M2ExtendedParticle> content;<br />
} exp2;<br />
</source><br />
<br />
<br />
The length of this M2Array is the same as length of particle_emitters<br />
<br />
===PABC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Appears to replace {{Unverified|[[#Animation_Lookup]]}} from old file. {{Unverified|Exact build unknown}}}}<br />
M2Array<uint16t> m_replacementParentSequenceLookups; // only seen in quillboarbrute*.m2. Contains AnimationIds<br />
<br />
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.<br />
<br />
This chunk called BlacklistAnimData in client.<br />
<br />
Client doesnt seem to use found index and thus whole array is used only to check if the target animation is present.<br />
<br />
M2InitBlacklistAnimData: sequenceIds<br />
<br />
===PADC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note=Moves texture weights from old file to a chunk. {{Unverified|Exact build unknown}}}}<br />
Defines replacement for header.texture_weights (WHY?)<br />
<br />
<source lang="cpp"><br />
struct PADC {<br />
M2Array<M2TextureWeight> texture_weights;<br />
} <br />
</source><br />
<br />
M2InitParentAnimData: parentTextureWeights<br />
<br />
===PSBC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}<br />
<br />
Defines ParentSequenceBounds<br />
M2Array<M2Bounds> parentSequenceBounds;<br />
<br />
M2InitParentSequenceBoundsData: M2Bounds<br />
<br />
===PEDC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.3.???|note={{Unverified|Exact build unknown}}}}<br />
M2Array<M2TrackBase> parentEventData;<br />
<br />
M2InitParentEventData: eventTracks<br />
<br />
===SKID===<br />
{{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}}}}<br />
uint32_t SKeletonfileID; // links to [[M2/.skel]]<br />
<br />
===TXID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629|note=Replaces the filename for {{#Textures}} with hardcoded type.}}<br />
Replaces in-file texture filenames. <br />
struct {<br />
uint32_t fileDataID;<br />
} textureID[]<br />
<br />
===LDV1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.0.1.26629}}<br />
<br />
Defines LodData<br />
<br />
<source lang="cpp"><br />
struct LodData<br />
{<br />
uint16 unk0; <br />
uint16 lodCount; //maxLod = lodCount-1; <br />
float unk2_f;<br />
uint8_t particleBoneLod[4]; //lod serves as indes into this array<br />
_DWORD unk4;<br />
};<br />
</source><br />
<br />
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<br />
<br />
unk2_f is used in formula, but it's purpose is unknown<br />
fmaxf(fminf(740.0 / unk2_f, 5.0), 0.5);<br />
<br />
LodData.particleBoneLod works this way: Each model has current lod which is [0..3]. Next:<br />
<source lang="cpp"><br />
if ( lod < 1 )<br />
result = 0;<br />
<br />
if ( LodData)<br />
result = (0x10000 << LodData->particleBoneLod[lod]);<br />
else<br />
result = (0x10000 << (lod- 1));<br />
<br />
...<br />
//For each ParticleEmitter and related M2Particle record<br />
if ( result & M2CompBone[M2Particle->old.boneIndex].flags ) {<br />
//Do not animate this emitter<br />
}<br />
</source><br />
<br />
<br />
[[User:Deamon|Deamon]] ([[User talk:Deamon|talk]])<br />
<br />
===RPID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>recursion_model_filename</tt>}}<br />
<br />
struct {<br />
uint32_t fileDataID;<br />
} recursive_particle_models[particle count];<br />
<br />
===GPID===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.1.0.27826|note=Replaces [[#M2ParticleOld]]'s <tt>geometry_model_filename</tt>}}<br />
<br />
struct {<br />
uint32_t fileDataID;<br />
} geometry_particle_models[particle count];<br />
<br />
===WFV1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}<br />
struct WFV1 {<br />
// unknown<br />
};<br />
<br />
===WFV2===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=8|min_build=8.2.0.30080}}<br />
struct WFV2 {<br />
// unknown<br />
};<br />
<br />
===PGD1===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=1|min_build=1.13.2.30172}}<br />
ParticleGeosetData<br />
<br />
struct PGD1Entry {<br />
char _0x00[2];<br />
};<br />
M2Array<PGD1Entry> p_g_d_v1; // count appears to be equivalent to particle count<br />
<br />
M2InitParticleGeosetData: m_emitterSelectionGroup<br />
<br />
===WFV3===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct WFV3 {<br />
// unknown<br />
};<br />
<br />
===PFDC===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
[[PHYS|PHYS]] physics;<br />
char PADDING[6]; // follows right after the last chunk in [[PHYS|PHYS]], or it could be data, only seen 0's<br />
Contains inline physics information in the same structure as the <tt>.phys</tt> files.<br />
<br />
===EDGF===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct EDGF {<br />
/*0x00*/ float _0x0[2];<br />
/*0x08*/ float _0x8;<br />
/*0x0C*/ char _0xC[0xC]<br />
} edgf[];<br />
<br />
===NERF===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=9|min_build=9.0.1.33978}}<br />
struct NERF {<br />
// unknown<br />
};<br />
<br />
=Skeleton and animation=<br />
==Global sequences==<br />
A list of timestamps that act as upper limits for global sequence ranges.<br />
struct M2Loop<br />
{<br />
uint32_t timestamp;<br />
} loops[];<br />
<br />
==Standard animation block==<br />
* {{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.<br />
* 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.<br />
*'''Many values that change with time are specified using blocks like the following.'''<br />
<br />
template<typename T><br />
struct M2Array<br />
{<br />
/*0x00*/ uint32_t number;<br />
/*0x04*/ uint32_t offset_elements;<br />
/*0x08*/<br />
};<br />
<br />
struct M2TrackBase<br />
{<br />
/*0x00*/ uint16_t interpolation_type;<br />
/*0x02*/ uint16_t global_sequence;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}}<br />
M2Array<pair<uint32_t>> interpolation_ranges; // no longer required {{Template:Sandbox/VersionRange|min_expansionlevel=3}}, as implicit by minimum and maximum timestamp per sequence.<br />
M2Array<uint32_t> timestamps;<br />
#else<br />
/*0x04*/ M2Array<M2Array<uint32_t>> timestamps;<br />
#endif<br />
/*0x0C*/<br />
};<br />
<br />
template<typename T><br />
struct M2Track : M2TrackBase<br />
{<br />
/*0x00*/ // base <br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=3|max_exclusive=1}} <br />
M2Array<T> values;<br />
#else<br />
/*0x0C*/ M2Array<M2Array<T>> values;<br />
#endif<br />
/*0x14*/<br />
};<br />
<br />
* Thus, as example, with<br />
<br />
{{Template:Type|M2CompBone|link=M2#Bones}} b;<br />
<br />
one may get the number of animations having translation information with<br />
<br />
b.translation.timestamps.number<br />
<br />
and the number of timestamps in the first animation using<br />
<br />
b.translation.timestamps.elements[0].number<br />
<br />
and the first timestamp value of the first animation via<br />
<br />
b.translation.timestamps.elements[0].elements[0]<br />
<br />
The actual translation vector for animation 0 at timestamp 0 is at<br />
<br />
b.translation.values.elements[0].elements[0]<br />
<br />
* Some timestamps/values.elements entries may have number/elements = 0, if for that animation id no animation is given.<br />
* [[#.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.<br />
* [model file name][animation id]-[animation sub-id][[#.anim_files|.anim]]<br />
* it seems like it is possible to detect if animation data is stored in-m2 or externally via<br />
** All animations which have flags & 0x20 are stored internally. <br />
** Animations which do not have flags & 0x20 are not stored internally. <br />
** Animations which do not have flags & 0x20 AND do not have flags & 0x40 are in [[#.anim_files|.anim]] files<br />
** Animations which do not have flags & 0x20 AND DO have flags 0x40 are stored... somewhere. I have no clue.<br />
<br />
===Global Sequences===<br />
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. <br />
<br />
A global sequence is completely unrelated to animations. It just always loops. This way, the sequence is not interrupted when an animation is launched.<br />
<br />
This appears to be frequently used by models that don't have more conventional animations (login screen animations, items/weapons with animated effects, etc).<br />
<br />
-- 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.<br />
<br />
===Interpolation===<br />
* If the interpolation type is 0, then values will change instantly at the timestamp, with no interpolation whatsoever.<br />
* If the interpolation type is 1, then the block linearly interpolates between keyframe values (lerp for vectors/colours, nlerp for quaternions).<br />
* 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>.<br />
* 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>.<br />
<br />
'''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)<br />
<br />
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)<br />
<br />
==.anim files==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=3}}<br />
<br />
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.<br />
<br />
'''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>.<br />
<br />
===Legion 24500===<br />
In Legion, these files are optionally chunked now. They are chunked either<br />
* if M2 header's 0x200000 flag is set and thus the new mid expansion format change is used<br />
* if the M2 has a .skel file<br />
<br />
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.<br />
<br />
====AFM2====<br />
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.<br />
====AFSA====<br />
skeleton data for attachments<br />
====AFSB====<br />
skeleton data for bones<br />
<br />
==Animation sequences==<br />
List of animations present in the model. <br />
struct M2Sequence<br />
{<br />
uint16_t id; // Animation id in [[AnimationData.dbc]]<br />
uint16_t variationIndex; // Sub-animation id: Which number in a row of animations this one is.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}}<br />
uint32_t start_timestamp;<br />
uint32_t end_timestamp;<br />
#else<br />
uint32_t duration; // The length of this animation sequence in milliseconds.<br />
#endif<br />
float movespeed; // This is the speed the character moves with in this animation.<br />
uint32_t flags; // See below.<br />
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).<br />
uint16_t _padding;<br />
M2Range replay; // May both be 0 to not repeat. Client will pick a random number of repetitions within bounds if given.<br />
#if version < ??? < 6.0.1<br />
uint32_t blendTime;<br />
#else<br />
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.<br />
uint16_t blendTimeOut; // The client blends between this sequence and the next sequence for blendTimeOut milliseconds.<br />
#endif<br />
// For both blendTimeIn and blendTimeOut, the client plays both sequences simultaneously while interpolating between their animation transforms.<br />
{{Template:Type|M2Bounds}} bounds;<br />
int16_t variationNext; // id of the following animation of this AnimationID, points to an Index or is -1 if none.<br />
uint16_t aliasNext; // id in the list of animations. Used to find actual animation if this sequence is an alias (flags & 0x40)<br />
} sequences[];<br />
<br />
--[[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 .<br />
===Flags===<br />
''One thing I saw in the source is that "-1 animationblocks" in bones wont get parsed if 0x20 is not set.''<br />
<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Flag !! width="500" | Description<br />
|-<br />
| 0x01 || Sets 0x80 when loaded. (M2Init)<br />
|-<br />
| 0x02 ||<br />
|-<br />
| 0x04 ||<br />
|-<br />
| 0x08 ||<br />
|-<br />
| 0x10 || apparently set during runtime in CM2Shared::LoadLowPrioritySequence for all entries of a loaded sequence (including aliases)<br />
|-<br />
| 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.}}<br />
|-<br />
| 0x40 || has next / is alias (To find the animation data, the client skips these by following aliasNext until an animation without 0x40 is found.)<br />
|-<br />
| 0x80 || Blended animation (if either side of a transition has 0x80, lerp between end->start states, unless end==start by comparing bone values)<br />
|-<br />
| 0x100 || sequence stored in model ?<br />
|-<br />
| 0x200 ||<br />
|-<br />
| 0x400 ||<br />
|-<br />
| 0x800 || seen in Legion 24500 models<br />
|}<br />
<br />
-- 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.<br />
<br />
=== Animation Lookup===<br />
Hash table for Animations in [[AnimationData.dbc]].<br />
<br />
struct<br />
{<br />
uint16_t animation_index; // Index at [[#Animation_sequences|ofsAnimations]] which represents the animation in [[AnimationData.dbc]]. -1 if none.<br />
} animation_lookups[];<br />
<br />
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:<br />
<br />
<source lang="c++"><br />
M2Sequence* find_entry (uint32_t anim_id)<br />
{<br />
size_t i (anim_id % animation_lookups.count);<br />
<br />
for (size_t stride (1); true; ++stride)<br />
{<br />
if (animation_lookups[i] == -1)<br />
{<br />
return nullptr;<br />
}<br />
if (animation_sequences[animation_lookups[i]].id == anim_id)<br />
{<br />
return &animation_sequences[i];<br />
}<br />
<br />
i = (i + stride * stride) % animation_lookups.count;<br />
// so original_i + 1, original_i + 1 + 4, original_i + 1 + 4 + 9, …<br />
}<br />
<br />
[[unreachable]];<br />
}<br />
</source><br />
<br />
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.<br />
<br />
=== Playable Animation Lookup ===<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=2|note=Partially inlined into M2Sequences}}<br />
<br />
Lookup table for Playable Animation in [[AnimationData.dbc]]<br />
<br />
'''Offset Type Description'''<br />
0x00 int16 Fallback Animation ID in [[AnimationData.dbc]]<br />
0x02 int16 Flags (0, 1, 3 seen)<br />
<br />
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.<br />
<br />
Flags are used to modify how the clip should be played:<br />
'''Value Meaning'''<br />
0 Play normal<br />
1 Play backwards?<br />
3 Freeze<br />
<br />
For instance, the "HumanMale/HumanMale.m2" model has just one "Loot" animation sequence. "LootHold" and "LootUp" are obtained with flags 3 and 1.<br />
<br />
== Bones ==<br />
struct M2CompBone // probably M2Bone {{Template:Sandbox/VersionRange|max_expansionlevel=1}}<br />
{<br />
int32_t key_bone_id; // Back-reference to [[#Key-Bone_Lookup|the key bone lookup table]]. -1 if this is no key bone.<br />
enum<br />
{<br />
spherical_billboard = 0x8,<br />
cylindrical_billboard_lock_x = 0x10,<br />
cylindrical_billboard_lock_y = 0x20,<br />
cylindrical_billboard_lock_z = 0x40,<br />
transformed = 0x200,<br />
kinematic_bone = 0x400, // MoP+: allow [[PHYS|physics]] to influence this bone<br />
helmet_anim_scaled = 0x1000, // set blend_modificator to [[HelmetAnimScaling.dbc|helmetAnimScalingRec]].m_amount for this bone<br />
};<br />
uint32_t flags; <br />
int16_t parent_bone; // Parent bone ID or -1 if there is none.<br />
uint16_t [[M2/.skin#Submeshes|submesh_id]]; // Mesh part ID OR uDistToParent?<br />
union { // only {{Template:Sandbox/VersionRange|min_expansionlevel=2}}?<br />
struct {<br />
uint16_t uDistToFurthDesc;<br />
uint16_t uZRatioOfChain;<br />
} CompressData; // {{Template:Unverified|No model has ever had this part of the union used.}}<br />
uint32_t boneNameCRC; // these are for debugging only. their bone names match those in key bone lookup.<br />
};<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=1}}<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation;<br />
#else<br />
[[#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<br />
#endif<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scale;<br />
{{Template:Type|C3Vector}} pivot; // The pivot point of that bone.<br />
} bones[];<br />
The bone indices in the vertex definitions seem to index into this data.<br />
<br />
===Billboards===<br />
The billboarding bits are used for various things:<br />
* Light halos around lamps must always face the viewer<br />
* 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.<br />
<br />
Spherical and cylindrical billboard bits are mutually exclusive. Only one of them can be used for the bone.<br />
<br />
===Bone Lookup Table===<br />
Lookup table for bones referenced from [[M2/.skin#Submeshes|M2SkinSection]]. <br />
struct <br />
{<br />
uint16_t bone;<br />
} bone_lookup[];<br />
<br />
===Key-Bone Lookup===<br />
Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 27 for the most models. At static models it is mostly 1.<br />
struct <br />
{<br />
uint16_t bone; // -1 if none<br />
} key_bone_lookup[];<br />
<br />
Official list:<br />
*00 "ArmL"<br />
*01 "ArmR"<br />
*02 "ShoulderL"<br />
*03 "ShoulderR"<br />
*04 "SpineLow" / "Upper Body" in animkits, if not present use Head<br />
*05 "Waist"<br />
*06 "Head"<br />
*07 "Jaw"<br />
*08 "IndexFingerR"<br />
*09 "MiddleFingerR"<br />
*10 "PinkyFingerR"<br />
*11 "RingFingerR"<br />
*12 "ThumbR"<br />
*13 "IndexFingerL"<br />
*14 "MiddleFingerL"<br />
*15 "PinkyFingerL"<br />
*16 "RingFingerL"<br />
*17 "ThumbL"<br />
*18 "$BTH"<br />
*19 "$CSR"<br />
*20 "$CSL"<br />
*21 "_Breath"<br />
*22 "_Name"<br />
*23 "_NameMount"<br />
*24 "$CHD"<br />
*25 "$CCH"<br />
*26 "Root"<br />
*27 "Wheel1" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*28 "Wheel2" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*29 "Wheel3" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*30 "Wheel4" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*31 "Wheel5" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*32 "Wheel6" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*33 "Wheel7" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
*34 "Wheel8" {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
<br />
Hierarchy requirement from animkits:<br />
* Full Body (-1 or 26 or 5)<br />
** Upper Body (4)<br />
*** Right Shoulder (2)<br />
**** Right Arm (1)<br />
***** Right Hand (8 or 9 or 10 or 11 or 12)<br />
*** Left Shoulder (3)<br />
**** Left Arm (0)<br />
***** Left Hand (13 or 14 or 15 or 16 or 17)<br />
*** Head (6)<br />
**** Jaw (7)<br />
** Wheel1…8 (27…34)<br />
<br />
=Geometry and rendering=<br />
==Vertices==<br />
struct M2Vertex<br />
{<br />
{{Template:Type|C3Vector}} pos;<br />
uint8 bone_weights[4];<br />
uint8 bone_indices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} tex_coords[2]; // two textures, depending on shader used<br />
};<br />
<br />
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).<br />
<br />
-- 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.<br />
<br />
==Views (LOD)==<br />
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.<br />
<br />
{{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.<br />
<br />
==Render flags==<br />
struct M2Material<br />
{<br />
uint16_t flags;<br />
uint16_t blending_mode; // apparently a bitfield<br />
} materials[];<br />
*'''Flags:'''<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Flag !! width="1000" | Meaning<br />
|-<br />
| 0x01 || Unlit<br />
|-<br />
| 0x02 || Unfogged<br />
|-<br />
| 0x04 || Two-sided (no backface culling if set)<br />
|-<br />
| 0x08 || depthTest<br />
|-<br />
| 0x10 || depthWrite<br />
|-<br />
| 0x40 || shadow batch related ??? (seen in WoD)<br />
|-<br />
| 0x80 || shadow batch related ??? (seen in WoD)<br />
|-<br />
| 0x400 || ??? (seen in WoD)<br />
|-<br />
| 0x800 || prevent alpha for custom elements. if set, use (fully) opaque or transparent. (litSphere, shadowMonk) (MoP+)<br />
|}<br />
*'''Blending mode'''<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! source !! dest !! notes<br />
|-<br />
| 0 || GL_ONE || GL_ZERO || blending disabled (GxBlendStateDesc: 0)<br />
|-<br />
| 1 || GL_ONE || GL_ZERO || Mod (1)<br />
|-<br />
| 2 || GL_SRC_ALPHA || GL_ONE_MINUS_SRC_ALPHA || Decal (2)<br />
|-<br />
| 3 || GL_ONE || GL_ONE || Add (10)<br />
|-<br />
| 4 || GL_SRC_ALPHA || GL_ONE || Mod2x (3)<br />
|-<br />
| 5 || GL_DST_COLOR || GL_ZERO || Fade (4)<br />
|-<br />
| 6 || GL_DST_COLOR || GL_SRC_COLOR || Deeprun Tram glass (5)<br />
|-<br />
| 7 || GL_ONE || GL_ONE_MINUS_SRC_ALPHA || WoD+ (13)<br />
|}<br />
<br />
(blend equation is always GL_FUNC_ADD. Values are retrieved via GxBlendStateDesc's lower 5 bits. no separate blend func for alpha.)<br />
*'''Blending mode'''<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="100" | Mapped to !! width="1500" | Meaning<br />
|-<br />
| 0 || 0 || Combiners_Opaque (Blend disabled)<br />
|-<br />
| 1 || 1 || Combiners_Mod (Blend enabled, Src = ONE, Dest = ZERO, SrcAlpha = ONE, DestAlpha = ZERO)<br />
|-<br />
| 2 || 1 || Combiners_Decal (Blend enabled, Src = SRC_ALPHA, Dest = INV_SRC_ALPHA, SrcAlpha = SRC_ALPHA, DestAlpha = INV_SRC_ALPHA )<br />
|-<br />
| 3 || 1 || Combiners_Add (Blend enabled, Src = SRC_COLOR, Dest = DEST_COLOR, SrcAlpha = SRC_ALPHA, DestAlpha = DEST_ALPHA )<br />
|-<br />
| 4 || 1 || Combiners_Mod2x (Blend enabled, Src = SRC_ALPHA, Dest = ONE, SrcAlpha = SRC_ALPHA, DestAlpha = ONE )<br />
|-<br />
| 5 || 4 || Combiners_Fade (Blend enabled, Src = SRC_ALPHA, Dest = INV_SRC_ALPHA, SrcAlpha = SRC_ALPHA, DestAlpha = INV_SRC_ALPHA )<br />
|-<br />
| 6 || 4 || Used in the Deeprun Tram subway glass, supposedly (Blend enabled, Src = DEST_COLOR, Dest = SRC_COLOR, SrcAlpha = DEST_ALPHA, DestAlpha = SRC_ALPHA )<br />
|-<br />
| 7 ||? || New in WoD, needs research! ''Example model: World\Expansion05\Doodads\Shadowmoon\Doodads\6FX_Fire_Grassline_Doodad_blue_LARGE.m2''<br />
|}<br />
''*Blend values are taken from D3D11 debugging of the client<br />
===Blend mode overrides===<br />
If this block is present (globalflags&8) and the "shading" flags of a textureunit wont be &0x8000, blending modes wont get mapped to the values above but to the ones in this block.<br />
<br />
Instead of Mapping[renderflags->blendingmode] it will be UnknownBlock[textureunit->Shading].<br />
<br />
As shading is not &0x8000 and (in their code) needs to be above 0, this may only touch Diffuse_T1.<br />
<br />
According to wod, if the M2 Header has flag 0x08, instead of reading blend mode from M2 [[#Render_flags|RenderFlags]], blendMode is read from the raw blend maps referenced in header.<br />
<br />
var flags = renderFlags[texUnit.renderFlags];<br />
var blendMode = flags >> 16;<br />
if ((header.GlobalModelFlags & 0x08) != 0 && texUnit.shader_id < mBlendMap.Length)<br />
blendMode = mBlendMap[texUnit.shader_id];<br />
<br />
==Texture unit lookup table==<br />
{{Template:SectionBox/VersionRange|max_expansionlevel=4|max_exclusive=1|note=Still present but unused in Cataclysm}}<br />
struct <br />
{<br />
uint16_t unit; // -1, 0, or 1. see below<br />
} texture_units[];<br />
<br />
For models that use multitexturing, this maps given texture unit numbers into actual texture unit numbers (0 or 1).<br />
<br />
Values of -1 seem to mean environment mapping.<br />
<br />
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.<br />
<br />
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?)<br />
<br />
As a side note, on my (dated) system WoW does every texture unit in a single pass.<br />
<br />
==Colors and transparency==<br />
===Colors===<br />
struct M2Color<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> color; // vertex colors in rgb order<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alpha; // 0 - transparent, 0x7FFF - opaque. Normaly NonInterp<br />
} colors[];<br />
<br />
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.<br />
<br />
===Transparency===<br />
struct M2TextureWeight<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> weight;<br />
} textureWeights[];<br />
<br />
Specifies global transparency values in addition to the values given in the Color block. I assume these are multiplied together eventually.<br />
====Transparency lookup table====<br />
struct <br />
{<br />
uint16_t transparency;<br />
} transparency_lookup[];<br />
Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.<br />
<br />
==Textures==<br />
*'''Textures are defined globally in a list''', additionally, a lookup table is given, referenced during rendering, to select textures.<br />
struct M2Texture<br />
{<br />
uint32_t type; // see below<br />
uint32_t flags; // see below<br />
M2Array<char> filename; // for non-hardcoded textures (type != 0), this still points to a zero-byte-only string.<br />
} textures[];<br />
<br />
Note that at least {{Template:Sandbox/PrettyVersion|expansionlevel=6|build=6.0.1.18179}} relies on both<br />
<br />
* non-type-0 textures having a one-byte sized string that has a first byte of <tt>\0</tt><br />
* 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.<br />
<br />
Thus, <tt>filename</tt>'s size '''shall contain''' the zero byte, i.e. <tt>buffer size</tt> rather than <tt>string length</tt>.<br />
<br />
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.<br />
<br />
====Texture Types====<br />
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:<br />
*DBFilesClient\[[CharSections.dbc]]<br />
*DBFilesClient\[[CreatureDisplayInfo.dbc]]<br />
*DBFilesClient\[[ItemDisplayInfo.dbc]]<br />
*(possibly more)<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="800" | Meaning<br />
|-<br />
| 0 || - NONE - -- Texture given in filename<br />
|-<br />
| 1 || TEX_COMPONENT_SKIN -- Skin -- Body + clothes<br />
|-<br />
| 2 || TEX_COMPONENT_OBJECT_SKIN -- Object Skin -- Item, Capes ("Item\ObjectComponents\Cape\*.blp")<br />
|-<br />
| 3 || TEX_COMPONENT_WEAPON_BLADE -- Weapon Blade -- Used on several models but not used in the client as far as I see. Armor Reflect?<br />
|-<br />
| 4 || TEX_COMPONENT_WEAPON_HANDLE -- Weapon Handle<br />
|-<br />
| 5 || TEX_COMPONENT_ENVIRONMENT -- (OBSOLETE) Environment (Please remove from source art)<br />
|-<br />
| 6 || TEX_COMPONENT_CHAR_HAIR -- Character Hair<br />
|-<br />
| 7 || TEX_COMPONENT_CHAR_FACIAL_HAIR -- (OBSOLETE) Character Facial Hair (Please remove from source art)<br />
|-<br />
| 8 || TEX_COMPONENT_SKIN_EXTRA -- Skin Extra<br />
|-<br />
| 9 || TEX_COMPONENT_UI_SKIN -- UI Skin -- Used on inventory art M2s (1): inventoryartgeometry.m2 and inventoryartgeometryold.m2<br />
|-<br />
| 10 || TEX_COMPONENT_TAUREN_MANE -- (OBSOLETE) Tauren Mane (Please remove from source art) -- Only used in quillboarpinata.m2. I can't even find something referencing that file. Oo Is it used?<br />
|-<br />
| 11 || TEX_COMPONENT_MONSTER_1 -- Monster Skin 1 -- Skin for creatures or gameobjects #1<br />
|-<br />
| 12 || TEX_COMPONENT_MONSTER_2 -- Monster Skin 2 -- Skin for creatures or gameobjects #2<br />
|-<br />
| 13 || TEX_COMPONENT_MONSTER_3 -- Monster Skin 3 -- Skin for creatures or gameobjects #3<br />
|-<br />
| 14 || TEX_COMPONENT_ITEM_ICON -- Item Icon -- Used on inventory art M2s (2): ui-button.m2 and forcedbackpackitem.m2 (CSimpleModel_ReplaceIconTexture("texture"))<br />
|-<br />
| 15 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Background Color<br />
|-<br />
| 16 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem Color<br />
|-<br />
| 17 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Border Color<br />
|-<br />
| 18 || {{Template:Sandbox/VersionRange|min_expansionlevel=4}} Guild Emblem<br />
|}<br />
<br />
====Flags====<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="200" | Meaning<br />
|-<br />
| 0 || None<br />
|-<br />
| 1 || Texture wrap X<br />
|-<br />
| 2 || Texture wrap Y<br />
|-<br />
| 3 || Texture wrap XY<br />
|}<br />
<br />
====Texture lookup table====<br />
struct <br />
{<br />
uint16_t texture;<br />
} texture_lookup[];<br />
<br />
====Replacable texture lookup====<br />
struct <br />
{<br />
uint16_t replacement;<br />
} texture_replacements[];<br />
<br />
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.<br />
<br />
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.<br />
<br />
=Effects=<br />
==Texture Transforms==<br />
*'''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.<br />
<br />
struct M2TextureTransform<br />
{<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> translation;<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C4Quaternion}}> rotation; // rotation center is texture center (0.5, 0.5)<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> scaling;<br />
} textureTransforms[];<br />
<br />
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 <br />
<br />
So to get the proper UV rotation it would be necessary apply rotation this way:<br />
<br />
* Translate UV anim matrix to point (0.5, 0.5)<br />
* Apply rotation mat from quaternion<br />
* UV anim matrix to point (-0.5, -0.5)<br />
<br />
====Texture Transforms lookup table ====<br />
struct <br />
{<br />
uint16_t anim_texture_id; // -1 for static<br />
} anim_texture_lookup[];<br />
<br />
== Ribbon emitters ==<br />
struct M2Ribbon<br />
{<br />
uint32_t ribbonId; // Always (as I have seen): -1.<br />
uint32_t boneIndex; // A bone to attach to.<br />
{{Template:Type|C3Vector}} position; // And a position, relative to that bone.<br />
M2Array<uint16_t> textureIndices; // into [[#Textures|textures]]<br />
M2Array<uint16_t> materialIndices; // into [[#Render_flags|materials]]<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> colorTrack;<br />
[[#Standard_animation_block|M2Track]]<{{Template:Type|fixed16}}> alphaTrack; // And an alpha value in a short, where: 0 - transparent, 0x7FFF - opaque.<br />
[[#Standard_animation_block|M2Track]]<float> heightAboveTrack;<br />
[[#Standard_animation_block|M2Track]]<float> heightBelowTrack; // do not set to same!<br />
float edgesPerSecond; // this defines how smooth the ribbon is. A low value may produce a lot of edges.<br />
float edgeLifetime; // the length aka Lifespan. in seconds<br />
float gravity; // use arcsin(val) to get the emission angle in degree<br />
uint16_t textureRows; // tiles in texture<br />
uint16_t textureCols;<br />
[[#Standard_animation_block|M2Track]]<uint16_t> texSlotTrack;<br />
[[#Standard_animation_block|M2Track]]<uchar> visibilityTrack;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}} // TODO: verify version<br />
int16_t priorityPlane;<br />
uint16_t padding;<br />
#endif<br />
} ribbons[];<br />
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.<br />
<br />
''Parameters from the MDL format that are probably in here somewhere: emission rate, rows, cols ...?''<br />
<br />
== Particle emitters ==<br />
'''This is partly wrong as hell!''' Do not rely on this block, at all. It might even be wrong for WotLK.<br />
===M2ParticleOld===<br />
struct M2ParticleOld {<br />
uint32 particleId; // Always (as I have seen): -1.<br />
uint32 flags; // See Below<br />
{{Template:Type|C3Vector}} Position; // The position. Relative to the following bone.<br />
uint16 bone; // The [[#Bones|bone]] its attached to.<br />
union<br />
{<br />
uint16 texture; // And the [[#Texture_definitions|textures]] that are used. <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
struct // For multi-textured particles actually three ids<br />
{<br />
uint16_t texture_0 : 5;<br />
uint16_t texture_1 : 5;<br />
uint16_t texture_2 : 5;<br />
uint16_t : 1;<br />
};<br />
#endif<br />
};<br />
M2Array<char> geometry_model_filename; // if given, this emitter spawns models<br />
M2Array<char> recursion_model_filename; // if given, this emitter is an '''alias''' for the (maximum 4) emitters of the given model<br />
<br />
#if >= 263 (late Burning Crusade)<br />
uint8 blendingType; // A blending type for the particle. See Below<br />
uint8 emitterType; // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone<br />
uint16 particleColorIndex; // This one is used for [[ParticleColor.dbc]]. See below.<br />
#else<br />
uint16 blendingType; // A blending type for the particle. See Below<br />
uint16 emitterType; // 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone<br />
#endif<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
{{Template:Type|fixed_point}}<uint8_t, 2, 5> multiTextureParamX[2];<br />
#else<br />
uint8 particleType; // Found below.<br />
uint8 headorTail; // 0 - Head, 1 - Tail, 2 - Both <br />
#endif<br />
uint16 textureTileRotation; // Rotation for the texture tile. (Values: -1,0,1) -- priorityPlane<br />
uint16 textureDimensions_rows; // for tiled textures<br />
uint16 textureDimensions_columns;<br />
[[#Standard_animation_block|M2Track]]<float> emissionSpeed; // Base velocity at which particles are emitted.<br />
[[#Standard_animation_block|M2Track]]<float> speedVariation; // Random variation in particle emission speed. (range: 0 to 1)<br />
[[#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; <br />
// 0 makes the velocity straight up (+z). For sphere generators, this is the maximum elevation of the initial position; <br />
// 0 makes the initial position entirely in the x-y plane (z=0).<br />
[[#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; <br />
// 0 makes the velocity have no sideways (y-axis) component. <br />
// For sphere generators, this is the maximum azimuth angle of the initial position.<br />
[[#Standard_animation_block|M2Track]]<float> gravity; // Not necessarily a float; [[#Compressed Particle Gravity|see below]].<br />
[[#Standard_animation_block|M2Track]]<float> lifespan;<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float lifespanVary; // An individual particle's lifespan is added to by <code>lifespanVary * random(-1, 1)</code><br />
#endif<br />
[[#Standard_animation_block|M2Track]]<float> emissionRate; <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float emissionRateVary; // This adds to the base emissionRate value the same way as lifespanVary. The random value is different every update.<br />
#endif<br />
[[#Standard_animation_block|M2Track]]<float> emissionAreaLength; // For plane generators, this is the width of the plane in the x-axis.<br />
// For sphere generators, this is the minimum radius.<br />
[[#Standard_animation_block|M2Track]]<float> emissionAreaWidth; // For plane generators, this is the width of the plane in the y-axis.<br />
// For sphere generators, this is the maximum radius.<br />
[[#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><br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C3Vector}}> colorTrack; // Most likely they all have 3 timestamps for {start, middle, end}.<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|fixed16}}> alphaTrack;<br />
[[#The_Fake-AnimationBlock|FBlock]]<{{Template:Type|C2Vector}}> scaleTrack;<br />
{{Template:Type|C2Vector}} scaleVary; // A percentage amount to randomly vary the scale of each particle<br />
[[#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)<br />
[[#The_Fake-AnimationBlock|FBlock]]<uint16> tailCellTrack;<br />
#else<br />
float midPoint; // middleTime; Middle point in lifespan (0 to 1).<br />
{{Type|CImVector}}[3] colorValues; // start, middle, end<br />
float[3] scaleValues;<br />
uint16[3] lifespanUVAnim;<br />
uint16[3] decayUVAnim;<br />
int16[2] tailUVAnim; // start, end<br />
int16[2] tailDecayUVAnim;<br />
#endif<br />
float tailLength;<br />
float twinkleSpeed; // twinkleFPS; has something to do with the spread<br />
float twinklePercent; // same mechanic as MDL twinkleOnOff but non-binary in 0.11.0<br />
{{Template:Type|CRange}} twinkleScale; // min, max<br />
float burstMultiplier; // ivelScale; requires (flags & 0x40)<br />
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 ).<br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
float baseSpin; // Initial rotation of the particle quad<br />
float baseSpinVary;<br />
float spin; // Rotation of the particle quad per second<br />
float spinVary;<br />
#else<br />
float spin; // 0.0 for none, 1.0 to rotate the particle 360 degrees throughout its lifetime.<br />
#endif<br />
<br />
[[#M2Box|M2Box]] tumble;<br />
{{Template:Type|C3Vector}} windVector;<br />
float windTime;<br />
<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
M2Array<C3Vector> splinePoints; // Set only for spline praticle emitter. Contains array of points for spline<br />
[[#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.<br />
} particles[];<br />
<br />
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<br />
<br />
===M2Particle (Cata+)===<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=4}}<br />
* Cata+ has multi texture support<br />
using fp_6_9 = {{Template:Type|fixed_point}}<uint16_t, 6, 9>;<br />
struct vector_2fp_6_9 { fp_6_9 x; fp_6_9 y; };<br />
struct M2Particle<br />
{<br />
M2ParticleOld old;<br />
vector_2fp_6_9 multiTextureParam0[2];<br />
vector_2fp_6_9 multiTextureParam1[2];<br />
} particles[];<br />
<br />
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.<br />
<br />
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)<br />
<br />
===Particle Flags===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="600" | Description<br />
|-<br />
| 0x1 || Particles are affected by lighting; <br />
|-<br />
| 0x2 || <br />
|-<br />
| 0x4 || <br />
|-<br />
| 0x8 || Particles travel "up" in world space, rather than model.<br />
|-<br />
| 0x10 || Do not Trail<br />
|-<br />
| 0x20 || Unlightning<br />
|-<br />
| 0x40 || Use Burst Multiplier<br />
|-<br />
| 0x80 || Particles in Model Space<br />
|-<br />
| 0x100 || <br />
|-<br />
| 0x200 || spawn position randomized in some way?<br />
|-<br />
| 0x400 || STYLE: Pinned Particles, their quad enlarges from their creation position to where they expand.<br />
|-<br />
| 0x800 || <br />
|-<br />
| 0x1000 || XYQuad Particles. They align to XY axis facing Z axis direction.<br />
|-<br />
| 0x2000 || clamp to ground; call CParticleEmitter2::ProjectParticle<br />
|-<br />
| 0x4000 || <br />
|-<br />
| 0x8000 || <br />
|-<br />
| 0x10000 || ChooseRandomTexture<br />
|-<br />
| 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.<br />
|-<br />
| 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.<br />
|-<br />
| 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.<br />
|-<br />
| 0x200000 || Random FlipBookStart<br />
|-<br />
| 0x400000 || Ignores Distance (or 0x4000000?!, CMapObjDef::SetDoodadEmittersIgnoresDistance has this one)<br />
|-<br />
| 0x800000 || gravity values are compressed vectors instead of z-axis values (see Compressed Particle Gravity below)<br />
|-<br />
| 0x1000000 || bone generator = bone, not joint<br />
|-<br />
| 0x4000000 || do not throttle emission rate based on distance<br />
|-<br />
| 0x10000000 || Particle uses multi-texturing (could be one of the other WoD-specific flags), see multi-textured section.<br />
|}<br />
<br />
--[[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.<br />
<br />
===ParticleColorIndex===<br />
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.<br />
===Particle types===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="500" | Description<br />
|-<br />
| 0 || "normal" particle<br />
|-<br />
| 1 || large quad from the particle's origin to its position (used in Moonwell water effects)<br />
|-<br />
| 2 || seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)<br />
|}<br />
''ParticleType is always 0 and, maybe, now (Flags & 0x40000) != 0 means "particles from origin to position". --Igor''<br />
''Checked and verified --BlinkHawk''<br />
===Particle Blendings===<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="500" | Description<br />
|-<br />
| 0 || glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST);<br />
|-<br />
| 1 || glBlendFunc(GL_SRC_COLOR, GL_ONE);<br />
|-<br />
| 2 || glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
|-<br />
| 3 || glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST);<br />
|-<br />
| 4 || glBlendFunc(GL_SRC_ALPHA, GL_ONE);<br />
|}<br />
from Modelviewer source<br />
-- Rour, some WoD particle effects are using blend mode 0x7 here.<br />
===The Fake-AnimationBlock===<br />
*Its pretty much like the real one but without the "header".<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Offset !! width="90" | Type !! width="120" | Name !! width="500" | Description<br />
|-<br />
| 0x000 || uint32 || nTimestamps || The number of timestamps.<br />
|-<br />
| 0x004 || uint32 || ofsTimestamps || And the offset to them. The timestamps are shorts! (?)<br />
|-<br />
| 0x008 || uint32 || nKeys || The same number again. This time its the number of Keys / Values.<br />
|-<br />
| 0x00C || uint32 || ofsKeys || And their offset.<br />
|}<br />
But they're unable to change between different animations, so they directly point to the data.<br />
<br />
===Compressed Particle Gravity===<br />
Key values in the gravity track are decompressed at load time from a 4-byte value to a C3Vector.<br />
<br />
<nowiki><br />
struct CompressedParticleGravity {<br />
int8_t x, y;<br />
int16_t z;<br />
};<br />
for (/* each 4-byte value in the particle gravity track */) {<br />
float *pValue;<br />
C3Vector *pDst;<br />
if (particle.flags & 0x800000) {<br />
// interpret the 4 bytes at pValue as CompressedParticleGravity:<br />
CompressedParticleGravity v = new (pValue) CompressedParticleGravity();<br />
C3Vector dir = C3Vector(v.x, v.y, 0) * (1.0f / 128.0f);<br />
float z = sqrtf(1.0f - dir.Dot(dir));<br />
float mag = v.z * 0.04238648f;<br />
if (mag < 0) {<br />
z = -z;<br />
mag = -mag;<br />
}<br />
dir.z = z;<br />
dir *= mag;<br />
*pDst = dir;<br />
} else {<br />
*pDst = C3Vector(0, 0, -(*pValue));<br />
}<br />
}</nowiki><br />
<br />
<br />
===M2Box===<br />
struct M2Box {<br />
{{Template:Type|C3Vector}} ModelRotationSpeedMin;<br />
{{Template:Type|C3Vector}} ModelRotationSpeedMax;<br />
}<br />
<br />
=Miscellaneous=<br />
==Name==<br />
char name[];<br />
<br />
Informative name used for debugging purposes. Not used in retail clients.<br />
<br />
==Bounding volumes==<br />
These blocks give a simplified bounding volume for the model. Characters and creatures have just a simple box.<br />
===Vertices===<br />
This block defines the possible points used for the model. They are referenced in the triangles block later.<br />
struct <br />
{<br />
{{Template:Type|C3Vector}} position;<br />
} bounding_vertices[];<br />
<br />
===Triangles===<br />
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. <br />
<br />
struct <br />
{<br />
uint16_t index; // three entries pointing to vertices per triangle<br />
} bounding_triangles[];<br />
<br />
The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles.<br />
<br />
===Normals===<br />
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.<br />
<br />
As each vertex has a corresponding normal vector, it should be true that nBoundingNormals = nBoundingTriangles / 3.<br />
struct <br />
{<br />
{{Template:Type|C3Vector}} normal;<br />
} bounding_normals[];<br />
<br />
== Lights ==<br />
struct M2Light<br />
{<br />
/*0x00*/ uint16_t type; // Types are listed below.<br />
/*0x02*/ int16_t bone; // -1 if not attached to a bone<br />
/*0x04*/ {{Template:Type|C3Vector}} position; // relative to bone, if given<br />
/*0x10*/ [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> ambient_color;<br />
/*0x24*/ [[#Standard_animation_block|M2Track]]<float> ambient_intensity; // defaults to 1.0<br />
/*0x38*/ [[#Standard_animation_block|M2Track]]<{{Template:Type|C3Vector}}> diffuse_color;<br />
/*0x4C*/ [[#Standard_animation_block|M2Track]]<float> diffuse_intensity; // defaults to 1.0<br />
/*0x60*/ [[#Standard_animation_block|M2Track]]<float> attenuation_start;<br />
/*0x74*/ [[#Standard_animation_block|M2Track]]<float> attenuation_end;<br />
/*0x88*/ [[#Standard_animation_block|M2Track]]<uint8_t> visibility; // enabled?<br />
/*0x9C*/<br />
} lights[];<br />
<br />
Two light types:<br />
{| style="background:#FCFCFC; color:black"<br />
! width="70" | Value !! width="200" | Description<br />
|-<br />
| 0 || Directional<br />
|-<br />
| 1 || Point light<br />
|}<br />
<br />
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.<br />
<br />
==Cameras==<br />
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.<br />
<br />
struct M2Camera<br />
{<br />
uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table.<br />
#if {{Template:Sandbox/VersionRange|max_expansionlevel=4|max_exclusive=1}}<br />
float fov; // Diagonal FOV in radians. See below for conversion.<br />
#endif<br />
float far_clip;<br />
float near_clip;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> positions; // How the camera's position moves. Should be 3*3 floats.<br />
{{Template:Type|C3Vector}} position_base;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<{{Template:Type|C3Vector}}>> target_position; // How the target moves. Should be 3*3 floats.<br />
{{Template:Type|C3Vector}} target_position_base;<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<float>> roll; // The camera can have some roll-effect. Its 0 to 2*Pi. <br />
#if {{Template:Sandbox/VersionRange|min_expansionlevel=4}}<br />
[[#Standard_animation_block|M2Track]]<M2SplineKey<float>> FoV; //Diagonal FOV in radians. See below for conversion.<br />
#endif<br />
} cameras[];<br />
<br />
===Camera field of view===<br />
<br />
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:<br />
<br />
<syntaxhighlight lang="cpp"><br />
float vfov = dfov / sqrt(1.0 + pow(aspect, 2.0));<br />
</syntaxhighlight><br />
<br />
The aspect ratio used is determined by the rect being presented on (eg. the game window).<br />
<br />
Note that this formula makes an assumption that the screen being projected to is planar (ie a monitor).<br />
<br />
===Camera lookup table===<br />
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.<br />
<br />
"-1" type cameras are not referenced.<br />
<br />
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.<br />
<br />
struct<br />
{<br />
uint16_t camera;<br />
} camera_lookup[];<br />
<br />
== Attachments ==<br />
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.<br />
struct M2Attachment<br />
{<br />
uint32_t id; // Referenced in the [[#Attachment_Lookup|lookup-block]] below.<br />
uint16_t bone; // attachment base<br />
uint16_t unknown; // see BogBeast.m2 in vanilla for a model having values here<br />
{{Template:Type|C3Vector}} position; // relative to bone; Often this value is the same as bone's pivot point <br />
[[#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.<br />
} attachments[];<br />
<br />
Meaning depends on type of model. The following are for creatures/characters mainly:<br />
<br />
{| style="background:#FCFCFC; color:black"<br />
! width="50" | ID !! width="250" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
! width="50" | ID !! width="150" | Description<br />
|-<br />
| 0 || Shield / MountMain / ItemVisual0<br />
| 12 || Back<br />
| 24 || Special2<br />
| 36 || Bullet (version: somewhen after alpha)<br />
| 48 || RightFoot<br />
|-<br />
| 1 || HandRight / ItemVisual1<br />
| 13 || ShoulderFlapRight<br />
| 25 || Special3<br />
| 37 || SpellHandOmni (version: somewhen after alpha)<br />
| 49 || ShieldNoGlove<br />
|-<br />
| 2 || HandLeft / ItemVisual2<br />
| 14 || ShoulderFlapLeft<br />
| 26 || SheathMainHand<br />
| 38 || SpellHandDirected (version: somewhen after alpha)<br />
| 50 || SpineLow<br />
|-<br />
| 3 || ElbowRight / ItemVisual3<br />
| 15 || ChestBloodFront<br />
| 27 || SheathOffHand<br />
| 39 || VehicleSeat1 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 51 || AlteredShoulderR<br />
|-<br />
| 4 || ElbowLeft / ItemVisual4<br />
| 16 || ChestBloodBack<br />
| 28 || SheathShield<br />
| 40 || VehicleSeat2 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 52 || AlteredShoulderL<br />
|-<br />
| 5 || ShoulderRight<br />
| 17 || Breath<br />
| 29 || PlayerNameMounted<br />
| 41 || VehicleSeat3 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 53 || BeltBuckle {{Template:Sandbox/VersionRange|min_expansionlevel=5}}<br />
|-<br />
| 6 || ShoulderLeft<br />
| 18 || PlayerName<br />
| 30 || LargeWeaponLeft<br />
| 42 || VehicleSeat4 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 54 || SheathCrossbow<br />
|-<br />
| 7 || KneeRight<br />
| 19 || Base<br />
| 31 || LargeWeaponRight<br />
| 43 || VehicleSeat5 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 55 || HeadTop {{Template:Sandbox/VersionRange|min_expansionlevel=7}}<br />
|-<br />
| 8 || KneeLeft<br />
| 20 || Head<br />
| 32 || HipWeaponLeft<br />
| 44 || VehicleSeat6 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 57 || Backpack? {{Template:Sandbox/VersionRange|min_expansionlevel=8}}<br />
| || <br />
|-<br />
| 9 || HipRight<br />
| 21 || SpellLeftHand<br />
| 33 || HipWeaponRight<br />
| 45 || VehicleSeat7 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| 60 || Unknown {{Template:Sandbox/VersionRange|min_expansionlevel=8}}<br />
| || <br />
|-<br />
| 10 || HipLeft<br />
| 22 || SpellRightHand<br />
| 34 || Chest<br />
| 46 || VehicleSeat8 {{Template:Sandbox/VersionRange|min_expansionlevel=3}}<br />
| || <br />
|-<br />
| 11 || Helm<br />
| 23 || Special1<br />
| 35 || HandArrow<br />
| 47 || LeftFoot<br />
| || <br />
|}<br />
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).<br />
===Attachment Lookup===<br />
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.<br />
struct <br />
{<br />
uint16_t attachment;<br />
} attachment_lookup[];<br />
<br />
==Events==<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
struct M2Event<br />
{<br />
uint32_t identifier; // mostly a 3 character name prefixed with '$'.<br />
uint32_t data; // This data is passed when the event is fired. <br />
uint32_t bone; // Somewhere it has to be attached.<br />
{{Template:Type|C3Vector}} position; // Relative to that bone of course, animated. Pivot without animating.<br />
[[#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".<br />
} events[];<br />
===Possible Events===<br />
There are a lot more of them. I did not list all up to now.<br />
<br />
Bow AnimEvents include $BWA, $BWP, $BWR, $BWS (If present, all of these should be childed to the right hand).<br />
<br />
{| style="background:#FCFCFC || color:black"<br />
! width="50" | ID !! width="40" | data !! width="60" | what !! width="60" | Type !! width="200" | seen to be fired on !! width="600" | Description<br />
|-<br />
| $AH[0-3] || — || PlaySoundKit (customAttack[x]) || || || soundEffect ID is defined by CreatureSoundDataRec::m_customAttack[x]<br />
|-<br />
| $BMD || — || BowMissleDestination || RangedWeapon || ||<br />
|-<br />
| $AIM || || || Vehicles || CGUnit_C::ComputeMissileTrajectory || Position used as MissileFirePos.<br />
|-<br />
| $ALT || || anim_swap_event / DisplayTransition || Unit || || CUnitDisplayTransition_C::UpdateState(1) or CUnitDisplayTransition_C::HandleAnimSwapEvent<br />
|-<br />
| $BL[0-3] || — || FootstepAnimEventHit (left) || Unit || || Backwards<br />
|-<br />
| $BR[0-3] || — || FootstepAnimEventHit (right) || Unit || || Backwards<br />
|-<br />
| $BRT || — || PlaySoundKit (birth) || || || soundEffect ID is defined by CreatureSoundDatarec::m_birthSoundID<br />
|-<br />
| $BTH || — || Breath || Unit || All situations, where nothing happens or breathing. || Adds Special Unit Effect based on unit state (under water, in-snow, …)<br />
|-<br />
| $BWP || — || PlayRangedItemPull (Bow Pull) || Unit || LoadRifle, LoadBow || <br />
|-<br />
| $BWR || — || BowRelease || Unit || AttackRifle, AttackBow, AttackThrown || <br />
|-<br />
| $CAH || — || || Unit || Attack*, *Unarmed, ShieldBash, Special* || attack hold? CGUnit_C::HandleCombatAnimEvent<br />
|-<br />
| $CCH || — || || Unit || mostly not fired, AttackThrown || CEffect::DrawFishingString needs this on the model for getting the string attachments.<br />
|-<br />
| $CFM || — || || Unit || CGCamera::UpdateMountHeightOrOffset || CGCamera::UpdateMountHeightOrOffset: Only z is used. Non-animated. Not used if $CMA<br />
|-<br />
| $CHD || || || Unit || not fired || probably does not exist?!<br />
|-<br />
| $CMA || — || || Unit || || CGCamera::UpdateMountHeightOrOffset: Position for camera<br />
|-<br />
| $CPP || || PlayCombatActionAnimKit || || || parry, anims, depending on some state, also some callback which might do more<br />
|-<br />
| $CSD || soundEntryId || PlayEmoteSound || Unit || Emote* || <br />
|-<br />
| $CSL || — || release_missiles_on_next_update if has_pending_missiles (left) || Unit || AttackRifle, SpellCast*, ChannelCast* || "x is {L or R} (""Left/right hand"") (?)"<br />
|-<br />
| $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"") (?)"<br />
|-<br />
| $CSS || — || PlayWeaponSwooshSound || || sound played depends on CGUnit_C::GetWeaponSwingType<br />
|-<br />
| $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.<br />
|-<br />
| $CVS || || || ? || || Data: SoundEntriesAdvanced.dbc, Sound — Not present in 6.0.1.18179<br />
|-<br />
| $DSE || — || DestroyEmitter || MapObj || || <br />
|-<br />
| $DSL || soundEntryId || DoodadSoundLoop (low priority) | GO || || <br />
|-<br />
| $DSO || soundEntryId || DoodadSoundOneShot || GO || || <br />
|-<br />
| $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.<br />
|-<br />
| $EAC || — || object package state enter 3, exit 2, 4, 5 || || || <br />
|-<br />
| $EDC || — || object package state enter 5, exit 3, 4, 2 || || || <br />
|-<br />
| $EMV || — || object package state enter 4, exit 3, 2, 5 || || || <br />
|-<br />
| $ESD || — || PlayEmoteStateSound || Unit || || soundEffect ID is implicit by currently played emote<br />
|-<br />
| $EWT || — || object package state enter 2, exit 3, 4, 5 || || || <br />
|-<br />
| $FD[1-9] || — || PlayFidgetSound || || || CreatureSoundDataRec::m_soundFidget (only has 5 entries, so don’t use 6-9)<br />
|-<br />
| $FDX || — || PlayUnitSound (stand) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundStandID<br />
|-<br />
| $FL[0-3] || — || FootstepAnimEventHit (left) || || || Forward<br />
|-<br />
| $FR[0-3] || — || FootstepAnimEventHit (right) || || || Forward<br />
|-<br />
| $FSD || — || HandleFootfallAnimEvent || Unit || Walk, Run (multiple times), ... || Plays some sound. Footstep? Also seen at several emotes etc. where feet are moved. CGUnit_C::HandleFootfallAnimEvent<br />
|-<br />
| $GC[0-3] || — || GameObject_C_PlayAnimatedSound || || || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x + 6] ({Custom0, Custom1, Custom2, Custom3})<br />
|-<br />
| $GO[0-5] || — || GameObject_C_PlayAnimatedSound || || || soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x] ({Stand, Open, Loop, Close, Destroy, Opened})<br />
|-<br />
| $HIT || — || PlayWoundAnimKit || Unit || Attack*, *Unarmed, ShieldBash, Special* || soundEntryId depends on SpellVisualKit<br />
|-<br />
| $KVS || || || ? || || MapLoad.cpp -- not found in 6.0.1.18179<br />
|-<br />
| $RL[0-3] || — || FootstepAnimEventHit (left) || || || Running<br />
|-<br />
| $RR[0-3] || — || FootstepAnimEventHit (right) || || || Running<br />
|-<br />
| $SCD || — || PlaySoundKit (spellCastDirectedSound) || || || soundEffect ID is defined by CreatureSoundDataRec::m_spellCastDirectedSoundID<br />
|-<br />
| $SHK || spellEffectCameraShakesID || AddShake || GO || || <br />
|-<br />
| $SHL || — || ExchangeSheathedWeapon (left) || || Sheath, HipSheath || <br />
|-<br />
| $SHR || — || ExchangeSheathedWeapon (right) || || Sheath, HipSheath || <br />
|-<br />
| $SL[0-3] || — || FootstepAnimEventHit (left) || || Stop, (JumpEnd), (Shuffle*) || Stop<br />
|-<br />
| $SMD || — || PlaySoundKit (submerged) || || || soundEffect ID is defined by CreatureSoundDatarec::m_submergedSoundID<br />
|-<br />
| $SMG || — || PlaySoundKit (submerge) || || || soundEffect ID is defined by CreatureSoundDatarec::m_submergeSoundID<br />
|-<br />
| $SND || soundEntryId || PlaySoundKit (custom) || GO || || <br />
|-<br />
| $SR[0-3] || — || FootstepAnimEventHit (right) || || Stop, (JumpEnd), (Shuffle*) || Stop<br />
|-<br />
| $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)<br />
|-<br />
| $TRD || — || HandleSpellEventSound || Unit || EmoteWork*, UseStanding* || soundEffect ID is implicit by SpellRec<br />
|-<br />
| $VG[0-8] || — || HandleBoneAnimGrabEvent || || || <br />
|-<br />
| $VT[0-8] || — || HandleBoneAnimThrowEvent || || || <br />
|-<br />
| $WGG || — || PlayUnitSound (wingGlide) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingGlideID<br />
|-<br />
| $WL[0-3] || — || FootstepAnimEventHit (left) || || || <br />
|-<br />
| $WNG || — || PlayUnitSound (wingFlap) || || || soundEffect ID is defined by CreatureSoundDataRec::m_soundWingFlapID<br />
|-<br />
| $WR[0-3] || — || FootstepAnimEventHit (right) || || || <br />
|-<br />
| $WTB || — || || Weapons || || Weapon Trail Bottom position, also used for Bow String<br />
|-<br />
| $WTT || — || || Weapons || || Weapon Trail Top position<br />
|-<br />
| $WWG || || || ? || || Calls some function in the Object VMT. -- Not seen in 6.0.1.18179<br />
|-<br />
| DEST || || || ? || || exploding ballista, that one has a really fucked up block. Oo<br />
|-<br />
| POIN || || || Unit || not fired || Data: ?, seen on multiple models. Basilisk for example. (6801)<br />
|-<br />
| WHEE || || || ? || || Data: 601+, Used on wheels at vehicles.<br />
|-<br />
| BOTT || || || ? || || Purpose unknown. Seen in well_vortex01.m2<br />
|-<br />
| TOP || || || ? || || Purpose unknown. Seen in well_vortex01.m2<br />
|}<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=PHYS&diff=29112PHYS2020-02-15T11:58:26Z<p>Zee: /* BDY3, BDY4 */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
.phys files are [[Chunk|chunked]]. The files are used by Blizzard's Domino physics engine which got added to WoW in the fourth expansion (MoP). In build {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15464}}, there is one .phys file "item/objectcomponents/waist/buckle_panstart_a_01.phys". .phys files are an extension to M2s. The M2 requests a .phys file to be loaded by having GlobalModelFlags & 0x20 set.<br />
<br />
The main PHYS chunk is followed by an unordered sequence of unique chunks of the other types.<br />
<br />
// vec*: * floats<br />
// mat*x*: * times * floats.<br />
<br />
* 1 phys<br />
* n body<br />
** n shapes<br />
*** 1 box<br />
*** 1 capsule<br />
*** 1 sphere<br />
*** 1 polytope (version 3+)<br />
*** 1 (tree mesh [*])<br />
*** 1 (height field [*])<br />
* n joints<br />
** 1 weld<br />
** 1 spherical<br />
** 1 shoulder<br />
** 1 distance (version 2+)<br />
** 1 revolute (version 2+)<br />
** 1 prismatic (version 2+)<br />
** 1 (mouse [*])<br />
[*] supported by domino, but not available in wow<br />
<br />
One body is connected to one bone. Bodies are connected via joints. A joint is of type weld, spherical or shoulder. A body is constructed out of shapes. A shape is a box, capsule or sphere.<br />
<br />
=PHYS=<br />
short version;<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15???}}: 0<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20773}}: 1<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}}: 2<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.21063}}: 2* -- not a different version in file or client parsing, but changed semantics and chunk names<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21287}}: 3<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21846}}: 4<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24500}}: 5 -- this version does not change anything in parsing. it likely handles some existing field differently<br />
<br />
Loading is partially backwards compatible and fills up with default values if loading older versions. Since version 2* it no longer reuses chunk identifiers but has separate identifiers for versions (BDY2, SHP2, WLJ2). It still is able to parse old ones, and fills up with defaults.<br />
<br />
=PHYV (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
When this chunk is present there seem to be no other following chunks, probably defines a single collider? example: 7vs_detail_nightmareplant01_phys.phys<br />
struct<br />
{<br />
float _unk[0x6];<br />
} phyv[];<br />
<br />
=PHYT (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
uint32_t phyt; // default: 0<br />
<br />
=BODY, BDY2=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ char PADDING_a[2];<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short modelBoneIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapes_base; // starting at shapes[shapes_base]<br />
/*0x18*/ int shapes_count; // shapes_count shapes are in this body.<br />
#if version >= 2 // BDY2<br />
/*0x1c*/ float _x1c; // default 1.0<br />
#endif<br />
} bodies[];<br />
<br />
=BDY3, BDY4=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ unsigned short boneIndex;<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short shapeIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapesCount; // shapes_count shapes are in this body.<br />
/*0x18*/ float unk0;<br />
#if version >= 3 // BDY3<br />
/*0x1c*/ float _x1c; // default 1.0<br />
/*0x20*/ float drag; // default 0, maybe incorrect<br />
/*0x24*/ float unk1; // default 0, seems to be some sort of weight. <br />
// If version >= 3 and unk1 == 0 the body will be non kinematic even if the flag is set, it needs to get its transform from the parent bone.<br />
// See offhand_1h_artifactskulloferedar_d_06 where all the bodies have the kinematic flag<br />
/*0x28*/ float _x28; // default 0.89999998<br />
#endif<br />
#if version >= 4 // BDY4<br />
/*0x2c*/ char _x2c[0x04]; // default 0x00000000<br />
#endif<br />
} bodies[];<br />
<br />
=SHAP, SHP2=<br />
struct<br />
{<br />
/*0x00*/ short shapeType;<br />
enum<br />
{<br />
box = 0, // BOXS<br />
capsule = 1, // CAPS<br />
sphere = 2, // SPHS<br />
#if version >= 3<br />
polytope = 3, // PLYT<br />
#endif<br />
};<br />
/*0x02*/ short shapeIndex; // into the corresponding chunk<br />
/*0x04*/ char unk[4];<br />
/*0x08*/ float friction;<br />
/*0x0c*/ float restitution;<br />
/*0x10*/ float density;<br />
#if version >= 2 // SHP2<br />
/*0x14*/ uint32_t _x14; // default 0<br />
/*0x18*/ float _x18; // default 1.0<br />
/*0x1c*/ uint16_t _x1c; // default 0<br />
/*0x1e*/ uint16_t _x1e; // no default, padding?<br />
#endif<br />
} shapes[];<br />
<br />
=shapes=<br />
==BOXS==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 a;<br />
/*0x30*/ vec3 c;<br />
} boxShapes[];<br />
<br />
==CAPS==<br />
struct<br />
{<br />
vec3 localPosition1;<br />
vec3 localPosition2;<br />
float radius;<br />
} capsuleShapes[];<br />
<br />
==SPHS==<br />
struct<br />
{<br />
vec3 localPosition;<br />
float radius;<br />
} sphereShapes[];<br />
<br />
==PLYT (version 3+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.3.21287}}<br />
<br />
This chunk does it's own array handling since the second part of the chunk is raw data. Instead of splitting in a header and a data chunk, it is combining both parts. The header has fixed size entries, the data block has entries of size based on the corresponding header entry. The header also has fields that are only set on runtime which should be <tt>0</tt> in file and will be a pointer to the corresponding data upon loading.<br />
<br />
struct {<br />
/*0x00*/ uint32_t count;<br />
<br />
struct {<br />
/*0x00*/ uint32_t vertexCount; // Mostly 8<br />
/*0x04*/ char unk_04[0x4];<br />
/*0x08*/ uint64_t RUNTIME_08_ptr_data_0; // = &data[i].unk_0<br />
/*0x10*/ uint32_t count_10; // Mostly 6<br />
/*0x14*/ char unk_14[0x4];<br />
/*0x18*/ uint64_t RUNTIME_18_ptr_data_1; // = &data[i].unk_1<br />
/*0x20*/ uint64_t RUNTIME_20_ptr_data_2; // = &data[i].unk_2<br />
/*0x28*/ uint32_t nodeCount; // Mostly 24<br />
/*0x2c*/ char unk_2C[0x4];<br />
/*0x30*/ uint64_t RUNTIME_30_ptr_data_3; // = &data[i].unk_3<br />
/*0x38*/ float unk_38[6]; // not sure if floats: has e-08 values<br />
} header[count];<br />
<br />
struct { // NOTE: This is NOT a fixed size. Every entries' size depends on the header entry's value!<br />
vec3 vertices[header[i].vertexCount]; // The vertices that compose the convex hull mesh, this data is enough to generate the collision mesh<br />
struct {<br />
char unk_00[0x10]; // probably a vec4, but really e-07 values en masse<br />
} unk_1[header[i].count_10];<br />
char unk_2[header[i].count_10];<br />
struct {<br />
char unk; // 1 or -1<br />
char vertexIndex; // index in vertex list<br />
char unkIndex0; // index into the nodes<br />
char unkIndex1; // index into the nodes<br />
} nodes[header[i].nodeCount]; // a tree structure that connects the vertices together<br />
} data[count];<br />
} polytopeShapes;<br />
<br />
=joints=<br />
==JOIN==<br />
struct JOINEntry<br />
{<br />
unsigned int bodyAIdx;<br />
unsigned int bodyBIdx;<br />
char unk[4];<br />
enum<br />
{<br />
sphericalJoint = 0,<br />
shoulderJoint = 1,<br />
weldJoint = 2,<br />
#if version >= 2<br />
revoluteJoint = 3,<br />
prismaticJoint = 4,<br />
distanceJoint = 5,<br />
#endif<br />
};<br />
short jointType;<br />
short jointId; // reference into the corresponding chunk entries.<br />
} joints[];<br />
<br />
==WELJ, WLJ2==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 frameA;<br />
/*0x30*/ mat3x4 frameB;<br />
/*0x60*/ float angularFrequencyHz;<br />
/*0x64*/ float angularDampingRatio;<br />
#if version >= 2 // WLJ2<br />
/*0x68*/ float linearFrequencyHz; // default 0<br />
/*0x6c*/ float linearDampingRatio; // default 0<br />
#endif<br />
} weldJoints[];<br />
<br />
==SPHJ==<br />
struct SPHJEntry<br />
{<br />
vec3 anchorA;<br />
vec3 anchorB;<br />
float frictionTorque;<br />
} sphericalJointEntries[];<br />
<br />
==SHOJ==<br />
Note that even though this chunk is handled differently since version 2, it does not have a version 2* chunk name.<br />
struct SHOJEntry<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerTwistAngle;<br />
float upperTwistAngle;<br />
float coneAngle;<br />
#if version >= 2<br />
char _x6c[8]; // NO BACKWARDS COMPATIBILITY as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}} and {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24931}}! client always assumes new size!<br />
#endif<br />
} shoulderJoints[];<br />
<br />
==PRSJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerLimit;<br />
float upperLimit;<br />
float unk0;<br />
float unk1;<br />
float unk2;<br />
float unk3;<br />
} prismaticJoints[];<br />
<br />
==REVJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerAngle;<br />
float upperAngle;<br />
float unk0;<br />
uint32 unk1;<br />
} revoluteJoints[];<br />
<br />
==DSTJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
char _unk[0x1c];<br />
} distanceJoints[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=PHYS&diff=29111PHYS2020-02-15T11:56:58Z<p>Zee: /* BDY3, BDY4 */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
.phys files are [[Chunk|chunked]]. The files are used by Blizzard's Domino physics engine which got added to WoW in the fourth expansion (MoP). In build {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15464}}, there is one .phys file "item/objectcomponents/waist/buckle_panstart_a_01.phys". .phys files are an extension to M2s. The M2 requests a .phys file to be loaded by having GlobalModelFlags & 0x20 set.<br />
<br />
The main PHYS chunk is followed by an unordered sequence of unique chunks of the other types.<br />
<br />
// vec*: * floats<br />
// mat*x*: * times * floats.<br />
<br />
* 1 phys<br />
* n body<br />
** n shapes<br />
*** 1 box<br />
*** 1 capsule<br />
*** 1 sphere<br />
*** 1 polytope (version 3+)<br />
*** 1 (tree mesh [*])<br />
*** 1 (height field [*])<br />
* n joints<br />
** 1 weld<br />
** 1 spherical<br />
** 1 shoulder<br />
** 1 distance (version 2+)<br />
** 1 revolute (version 2+)<br />
** 1 prismatic (version 2+)<br />
** 1 (mouse [*])<br />
[*] supported by domino, but not available in wow<br />
<br />
One body is connected to one bone. Bodies are connected via joints. A joint is of type weld, spherical or shoulder. A body is constructed out of shapes. A shape is a box, capsule or sphere.<br />
<br />
=PHYS=<br />
short version;<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15???}}: 0<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20773}}: 1<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}}: 2<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.21063}}: 2* -- not a different version in file or client parsing, but changed semantics and chunk names<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21287}}: 3<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21846}}: 4<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24500}}: 5 -- this version does not change anything in parsing. it likely handles some existing field differently<br />
<br />
Loading is partially backwards compatible and fills up with default values if loading older versions. Since version 2* it no longer reuses chunk identifiers but has separate identifiers for versions (BDY2, SHP2, WLJ2). It still is able to parse old ones, and fills up with defaults.<br />
<br />
=PHYV (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
When this chunk is present there seem to be no other following chunks, probably defines a single collider? example: 7vs_detail_nightmareplant01_phys.phys<br />
struct<br />
{<br />
float _unk[0x6];<br />
} phyv[];<br />
<br />
=PHYT (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
uint32_t phyt; // default: 0<br />
<br />
=BODY, BDY2=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ char PADDING_a[2];<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short modelBoneIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapes_base; // starting at shapes[shapes_base]<br />
/*0x18*/ int shapes_count; // shapes_count shapes are in this body.<br />
#if version >= 2 // BDY2<br />
/*0x1c*/ float _x1c; // default 1.0<br />
#endif<br />
} bodies[];<br />
<br />
=BDY3, BDY4=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ unsigned short boneIndex;<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short shapeIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapesCount; // shapes_count shapes are in this body.<br />
/*0x18*/ float unk0;<br />
#if version >= 3 // BDY3<br />
/*0x1c*/ float _x1c; // default 1.0<br />
/*0x20*/ float drag; // default 0, maybe incorrect<br />
/*0x24*/ float unk1; // default 0, seems to be some sort of weight. <br />
// If version >= 3 and unk1 == 0 the body will be non kinematic even if the flag is set, it needs to get its transform from the parent bone.<br />
// See offhand_1h_artifactskulloferedar_d_06 where all the bodies have then kinematic flag<br />
/*0x28*/ float _x28; // default 0.89999998<br />
#endif<br />
#if version >= 4 // BDY4<br />
/*0x2c*/ char _x2c[0x04]; // default 0x00000000<br />
#endif<br />
} bodies[];<br />
<br />
=SHAP, SHP2=<br />
struct<br />
{<br />
/*0x00*/ short shapeType;<br />
enum<br />
{<br />
box = 0, // BOXS<br />
capsule = 1, // CAPS<br />
sphere = 2, // SPHS<br />
#if version >= 3<br />
polytope = 3, // PLYT<br />
#endif<br />
};<br />
/*0x02*/ short shapeIndex; // into the corresponding chunk<br />
/*0x04*/ char unk[4];<br />
/*0x08*/ float friction;<br />
/*0x0c*/ float restitution;<br />
/*0x10*/ float density;<br />
#if version >= 2 // SHP2<br />
/*0x14*/ uint32_t _x14; // default 0<br />
/*0x18*/ float _x18; // default 1.0<br />
/*0x1c*/ uint16_t _x1c; // default 0<br />
/*0x1e*/ uint16_t _x1e; // no default, padding?<br />
#endif<br />
} shapes[];<br />
<br />
=shapes=<br />
==BOXS==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 a;<br />
/*0x30*/ vec3 c;<br />
} boxShapes[];<br />
<br />
==CAPS==<br />
struct<br />
{<br />
vec3 localPosition1;<br />
vec3 localPosition2;<br />
float radius;<br />
} capsuleShapes[];<br />
<br />
==SPHS==<br />
struct<br />
{<br />
vec3 localPosition;<br />
float radius;<br />
} sphereShapes[];<br />
<br />
==PLYT (version 3+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.3.21287}}<br />
<br />
This chunk does it's own array handling since the second part of the chunk is raw data. Instead of splitting in a header and a data chunk, it is combining both parts. The header has fixed size entries, the data block has entries of size based on the corresponding header entry. The header also has fields that are only set on runtime which should be <tt>0</tt> in file and will be a pointer to the corresponding data upon loading.<br />
<br />
struct {<br />
/*0x00*/ uint32_t count;<br />
<br />
struct {<br />
/*0x00*/ uint32_t vertexCount; // Mostly 8<br />
/*0x04*/ char unk_04[0x4];<br />
/*0x08*/ uint64_t RUNTIME_08_ptr_data_0; // = &data[i].unk_0<br />
/*0x10*/ uint32_t count_10; // Mostly 6<br />
/*0x14*/ char unk_14[0x4];<br />
/*0x18*/ uint64_t RUNTIME_18_ptr_data_1; // = &data[i].unk_1<br />
/*0x20*/ uint64_t RUNTIME_20_ptr_data_2; // = &data[i].unk_2<br />
/*0x28*/ uint32_t nodeCount; // Mostly 24<br />
/*0x2c*/ char unk_2C[0x4];<br />
/*0x30*/ uint64_t RUNTIME_30_ptr_data_3; // = &data[i].unk_3<br />
/*0x38*/ float unk_38[6]; // not sure if floats: has e-08 values<br />
} header[count];<br />
<br />
struct { // NOTE: This is NOT a fixed size. Every entries' size depends on the header entry's value!<br />
vec3 vertices[header[i].vertexCount]; // The vertices that compose the convex hull mesh, this data is enough to generate the collision mesh<br />
struct {<br />
char unk_00[0x10]; // probably a vec4, but really e-07 values en masse<br />
} unk_1[header[i].count_10];<br />
char unk_2[header[i].count_10];<br />
struct {<br />
char unk; // 1 or -1<br />
char vertexIndex; // index in vertex list<br />
char unkIndex0; // index into the nodes<br />
char unkIndex1; // index into the nodes<br />
} nodes[header[i].nodeCount]; // a tree structure that connects the vertices together<br />
} data[count];<br />
} polytopeShapes;<br />
<br />
=joints=<br />
==JOIN==<br />
struct JOINEntry<br />
{<br />
unsigned int bodyAIdx;<br />
unsigned int bodyBIdx;<br />
char unk[4];<br />
enum<br />
{<br />
sphericalJoint = 0,<br />
shoulderJoint = 1,<br />
weldJoint = 2,<br />
#if version >= 2<br />
revoluteJoint = 3,<br />
prismaticJoint = 4,<br />
distanceJoint = 5,<br />
#endif<br />
};<br />
short jointType;<br />
short jointId; // reference into the corresponding chunk entries.<br />
} joints[];<br />
<br />
==WELJ, WLJ2==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 frameA;<br />
/*0x30*/ mat3x4 frameB;<br />
/*0x60*/ float angularFrequencyHz;<br />
/*0x64*/ float angularDampingRatio;<br />
#if version >= 2 // WLJ2<br />
/*0x68*/ float linearFrequencyHz; // default 0<br />
/*0x6c*/ float linearDampingRatio; // default 0<br />
#endif<br />
} weldJoints[];<br />
<br />
==SPHJ==<br />
struct SPHJEntry<br />
{<br />
vec3 anchorA;<br />
vec3 anchorB;<br />
float frictionTorque;<br />
} sphericalJointEntries[];<br />
<br />
==SHOJ==<br />
Note that even though this chunk is handled differently since version 2, it does not have a version 2* chunk name.<br />
struct SHOJEntry<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerTwistAngle;<br />
float upperTwistAngle;<br />
float coneAngle;<br />
#if version >= 2<br />
char _x6c[8]; // NO BACKWARDS COMPATIBILITY as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}} and {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24931}}! client always assumes new size!<br />
#endif<br />
} shoulderJoints[];<br />
<br />
==PRSJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerLimit;<br />
float upperLimit;<br />
float unk0;<br />
float unk1;<br />
float unk2;<br />
float unk3;<br />
} prismaticJoints[];<br />
<br />
==REVJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerAngle;<br />
float upperAngle;<br />
float unk0;<br />
uint32 unk1;<br />
} revoluteJoints[];<br />
<br />
==DSTJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
char _unk[0x1c];<br />
} distanceJoints[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=PHYS&diff=29097PHYS2020-02-14T15:14:15Z<p>Zee: /* BDY3, BDY4 */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
.phys files are [[Chunk|chunked]]. The files are used by Blizzard's Domino physics engine which got added to WoW in the fourth expansion (MoP). In build {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15464}}, there is one .phys file "item/objectcomponents/waist/buckle_panstart_a_01.phys". .phys files are an extension to M2s. The M2 requests a .phys file to be loaded by having GlobalModelFlags & 0x20 set.<br />
<br />
The main PHYS chunk is followed by an unordered sequence of unique chunks of the other types.<br />
<br />
// vec*: * floats<br />
// mat*x*: * times * floats.<br />
<br />
* 1 phys<br />
* n body<br />
** n shapes<br />
*** 1 box<br />
*** 1 capsule<br />
*** 1 sphere<br />
*** 1 polytope (version 3+)<br />
*** 1 (tree mesh [*])<br />
*** 1 (height field [*])<br />
* n joints<br />
** 1 weld<br />
** 1 spherical<br />
** 1 shoulder<br />
** 1 distance (version 2+)<br />
** 1 revolute (version 2+)<br />
** 1 prismatic (version 2+)<br />
** 1 (mouse [*])<br />
[*] supported by domino, but not available in wow<br />
<br />
One body is connected to one bone. Bodies are connected via joints. A joint is of type weld, spherical or shoulder. A body is constructed out of shapes. A shape is a box, capsule or sphere.<br />
<br />
=PHYS=<br />
short version;<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15???}}: 0<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20773}}: 1<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}}: 2<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.21063}}: 2* -- not a different version in file or client parsing, but changed semantics and chunk names<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21287}}: 3<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21846}}: 4<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24500}}: 5 -- this version does not change anything in parsing. it likely handles some existing field differently<br />
<br />
Loading is partially backwards compatible and fills up with default values if loading older versions. Since version 2* it no longer reuses chunk identifiers but has separate identifiers for versions (BDY2, SHP2, WLJ2). It still is able to parse old ones, and fills up with defaults.<br />
<br />
=PHYV (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
When this chunk is present there seem to be no other following chunks, probably defines a single collider? example: 7vs_detail_nightmareplant01_phys.phys<br />
struct<br />
{<br />
float _unk[0x6];<br />
} phyv[];<br />
<br />
=PHYT (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
uint32_t phyt; // default: 0<br />
<br />
=BODY, BDY2=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ char PADDING_a[2];<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short modelBoneIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapes_base; // starting at shapes[shapes_base]<br />
/*0x18*/ int shapes_count; // shapes_count shapes are in this body.<br />
#if version >= 2 // BDY2<br />
/*0x1c*/ float _x1c; // default 1.0<br />
#endif<br />
} bodies[];<br />
<br />
=BDY3, BDY4=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ unsigned short boneIndex;<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short shapeIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapesCount; // shapes_count shapes are in this body.<br />
/*0x18*/ float unk;<br />
#if version >= 3 // BDY3<br />
/*0x1c*/ float _x1c; // default 1.0<br />
/*0x20*/ float drag; // default 0, maybe incorrect<br />
/*0x24*/ float angularDrag; // default 0, maybe incorrect<br />
/*0x28*/ float _x28; // default 0.89999998<br />
#endif<br />
#if version >= 4 // BDY4<br />
/*0x2c*/ char _x2c[0x04]; // default 0x00000000<br />
#endif<br />
} bodies[];<br />
<br />
=SHAP, SHP2=<br />
struct<br />
{<br />
/*0x00*/ short shapeType;<br />
enum<br />
{<br />
box = 0, // BOXS<br />
capsule = 1, // CAPS<br />
sphere = 2, // SPHS<br />
#if version >= 3<br />
polytope = 3, // PLYT<br />
#endif<br />
};<br />
/*0x02*/ short shapeIndex; // into the corresponding chunk<br />
/*0x04*/ char unk[4];<br />
/*0x08*/ float friction;<br />
/*0x0c*/ float restitution;<br />
/*0x10*/ float density;<br />
#if version >= 2 // SHP2<br />
/*0x14*/ uint32_t _x14; // default 0<br />
/*0x18*/ float _x18; // default 1.0<br />
/*0x1c*/ uint16_t _x1c; // default 0<br />
/*0x1e*/ uint16_t _x1e; // no default, padding?<br />
#endif<br />
} shapes[];<br />
<br />
=shapes=<br />
==BOXS==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 a;<br />
/*0x30*/ vec3 c;<br />
} boxShapes[];<br />
<br />
==CAPS==<br />
struct<br />
{<br />
vec3 localPosition1;<br />
vec3 localPosition2;<br />
float radius;<br />
} capsuleShapes[];<br />
<br />
==SPHS==<br />
struct<br />
{<br />
vec3 localPosition;<br />
float radius;<br />
} sphereShapes[];<br />
<br />
==PLYT (version 3+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.3.21287}}<br />
<br />
This chunk does it's own array handling since the second part of the chunk is raw data. Instead of splitting in a header and a data chunk, it is combining both parts. The header has fixed size entries, the data block has entries of size based on the corresponding header entry. The header also has fields that are only set on runtime which should be <tt>0</tt> in file and will be a pointer to the corresponding data upon loading.<br />
<br />
struct {<br />
/*0x00*/ uint32_t count;<br />
<br />
struct {<br />
/*0x00*/ uint32_t vertexCount; // Mostly 8<br />
/*0x04*/ char unk_04[0x4];<br />
/*0x08*/ uint64_t RUNTIME_08_ptr_data_0; // = &data[i].unk_0<br />
/*0x10*/ uint32_t count_10; // Mostly 6<br />
/*0x14*/ char unk_14[0x4];<br />
/*0x18*/ uint64_t RUNTIME_18_ptr_data_1; // = &data[i].unk_1<br />
/*0x20*/ uint64_t RUNTIME_20_ptr_data_2; // = &data[i].unk_2<br />
/*0x28*/ uint32_t nodeCount; // Mostly 24<br />
/*0x2c*/ char unk_2C[0x4];<br />
/*0x30*/ uint64_t RUNTIME_30_ptr_data_3; // = &data[i].unk_3<br />
/*0x38*/ float unk_38[6]; // not sure if floats: has e-08 values<br />
} header[count];<br />
<br />
struct { // NOTE: This is NOT a fixed size. Every entries' size depends on the header entry's value!<br />
vec3 vertices[header[i].vertexCount]; // The vertices that compose the convex hull mesh, this data is enough to generate the collision mesh<br />
struct {<br />
char unk_00[0x10]; // probably a vec4, but really e-07 values en masse<br />
} unk_1[header[i].count_10];<br />
char unk_2[header[i].count_10];<br />
struct {<br />
char unk; // 1 or -1<br />
char vertexIndex; // index in vertex list<br />
char unkIndex0; // index into the nodes<br />
char unkIndex1; // index into the nodes<br />
} nodes[header[i].nodeCount]; // a tree structure that connects the vertices together<br />
} data[count];<br />
} polytopeShapes;<br />
<br />
=joints=<br />
==JOIN==<br />
struct JOINEntry<br />
{<br />
unsigned int bodyAIdx;<br />
unsigned int bodyBIdx;<br />
char unk[4];<br />
enum<br />
{<br />
sphericalJoint = 0,<br />
shoulderJoint = 1,<br />
weldJoint = 2,<br />
#if version >= 2<br />
revoluteJoint = 3,<br />
prismaticJoint = 4,<br />
distanceJoint = 5,<br />
#endif<br />
};<br />
short jointType;<br />
short jointId; // reference into the corresponding chunk entries.<br />
} joints[];<br />
<br />
==WELJ, WLJ2==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 frameA;<br />
/*0x30*/ mat3x4 frameB;<br />
/*0x60*/ float angularFrequencyHz;<br />
/*0x64*/ float angularDampingRatio;<br />
#if version >= 2 // WLJ2<br />
/*0x68*/ float linearFrequencyHz; // default 0<br />
/*0x6c*/ float linearDampingRatio; // default 0<br />
#endif<br />
} weldJoints[];<br />
<br />
==SPHJ==<br />
struct SPHJEntry<br />
{<br />
vec3 anchorA;<br />
vec3 anchorB;<br />
float frictionTorque;<br />
} sphericalJointEntries[];<br />
<br />
==SHOJ==<br />
Note that even though this chunk is handled differently since version 2, it does not have a version 2* chunk name.<br />
struct SHOJEntry<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerTwistAngle;<br />
float upperTwistAngle;<br />
float coneAngle;<br />
#if version >= 2<br />
char _x6c[8]; // NO BACKWARDS COMPATIBILITY as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}} and {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24931}}! client always assumes new size!<br />
#endif<br />
} shoulderJoints[];<br />
<br />
==PRSJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerLimit;<br />
float upperLimit;<br />
float unk0;<br />
float unk1;<br />
float unk2;<br />
float unk3;<br />
} prismaticJoints[];<br />
<br />
==REVJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerAngle;<br />
float upperAngle;<br />
float unk0;<br />
uint32 unk1;<br />
} revoluteJoints[];<br />
<br />
==DSTJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
char _unk[0x1c];<br />
} distanceJoints[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=PHYS&diff=29081PHYS2020-02-12T20:54:42Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|min_expansionlevel=5}}<br />
.phys files are [[Chunk|chunked]]. The files are used by Blizzard's Domino physics engine which got added to WoW in the fourth expansion (MoP). In build {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15464}}, there is one .phys file "item/objectcomponents/waist/buckle_panstart_a_01.phys". .phys files are an extension to M2s. The M2 requests a .phys file to be loaded by having GlobalModelFlags & 0x20 set.<br />
<br />
The main PHYS chunk is followed by an unordered sequence of unique chunks of the other types.<br />
<br />
// vec*: * floats<br />
// mat*x*: * times * floats.<br />
<br />
* 1 phys<br />
* n body<br />
** n shapes<br />
*** 1 box<br />
*** 1 capsule<br />
*** 1 sphere<br />
*** 1 polytope (version 3+)<br />
*** 1 (tree mesh [*])<br />
*** 1 (height field [*])<br />
* n joints<br />
** 1 weld<br />
** 1 spherical<br />
** 1 shoulder<br />
** 1 distance (version 2+)<br />
** 1 revolute (version 2+)<br />
** 1 prismatic (version 2+)<br />
** 1 (mouse [*])<br />
[*] supported by domino, but not available in wow<br />
<br />
One body is connected to one bone. Bodies are connected via joints. A joint is of type weld, spherical or shoulder. A body is constructed out of shapes. A shape is a box, capsule or sphere.<br />
<br />
=PHYS=<br />
short version;<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=5|build=5.0.1.15???}}: 0<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20773}}: 1<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}}: 2<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.21063}}: 2* -- not a different version in file or client parsing, but changed semantics and chunk names<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21287}}: 3<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.3.21846}}: 4<br />
// since {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24500}}: 5 -- this version does not change anything in parsing. it likely handles some existing field differently<br />
<br />
Loading is partially backwards compatible and fills up with default values if loading older versions. Since version 2* it no longer reuses chunk identifiers but has separate identifiers for versions (BDY2, SHP2, WLJ2). It still is able to parse old ones, and fills up with defaults.<br />
<br />
=PHYV (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
When this chunk is present there seem to be no other following chunks, probably defines a single collider? example: 7vs_detail_nightmareplant01_phys.phys<br />
struct<br />
{<br />
float _unk[0x6];<br />
} phyv[];<br />
<br />
=PHYT (version 1+)=<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20773}}<br />
uint32_t phyt; // default: 0<br />
<br />
=BODY, BDY2=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ char PADDING_a[2];<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short modelBoneIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapes_base; // starting at shapes[shapes_base]<br />
/*0x18*/ int shapes_count; // shapes_count shapes are in this body.<br />
#if version >= 2 // BDY2<br />
/*0x1c*/ float _x1c; // default 1.0<br />
#endif<br />
} bodies[];<br />
<br />
=BDY3, BDY4=<br />
struct<br />
{<br />
/*0x00*/ unsigned short type; // maps to dmBodyDef type enum. 0 -> 1, 1 -> 0 = dm_dynamicBody, * -> 2. Only one should be of type 0 (root). possibly only 0 and 1.<br />
/*0x02*/ unsigned short boneIndex;<br />
/*0x04*/ vec3 position;<br />
/*0x10*/ unsigned short shapeIndex;<br />
/*0x12*/ char PADDING_b[2];<br />
/*0x14*/ int shapesCount; // shapes_count shapes are in this body.<br />
/*0x18*/ float unk;<br />
#if version >= 3 // BDY3<br />
/*0x1c*/ float _x1c; // default 1.0<br />
/*0x20*/ char _x20[4]; // default 0<br />
/*0x24*/ float drag; // default 0 seems to be physics body drag, example: Anduin3's robe bounces too much without this<br />
/*0x28*/ float _x28; // default 0.89999998<br />
#endif<br />
#if version >= 4 // BDY4<br />
/*0x2c*/ char _x2c[0x04]; // default 0x00000000<br />
#endif<br />
} bodies[];<br />
<br />
=SHAP, SHP2=<br />
struct<br />
{<br />
/*0x00*/ short shapeType;<br />
enum<br />
{<br />
box = 0, // BOXS<br />
capsule = 1, // CAPS<br />
sphere = 2, // SPHS<br />
#if version >= 3<br />
polytope = 3, // PLYT<br />
#endif<br />
};<br />
/*0x02*/ short shapeIndex; // into the corresponding chunk<br />
/*0x04*/ char unk[4];<br />
/*0x08*/ float friction;<br />
/*0x0c*/ float restitution;<br />
/*0x10*/ float density;<br />
#if version >= 2 // SHP2<br />
/*0x14*/ uint32_t _x14; // default 0<br />
/*0x18*/ float _x18; // default 1.0<br />
/*0x1c*/ uint16_t _x1c; // default 0<br />
/*0x1e*/ uint16_t _x1e; // no default, padding?<br />
#endif<br />
} shapes[];<br />
<br />
=shapes=<br />
==BOXS==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 a;<br />
/*0x30*/ vec3 c;<br />
} boxShapes[];<br />
<br />
==CAPS==<br />
struct<br />
{<br />
vec3 localPosition1;<br />
vec3 localPosition2;<br />
float radius;<br />
} capsuleShapes[];<br />
<br />
==SPHS==<br />
struct<br />
{<br />
vec3 localPosition;<br />
float radius;<br />
} sphereShapes[];<br />
<br />
==PLYT (version 3+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.3.21287}}<br />
<br />
This chunk does it's own array handling since the second part of the chunk is raw data. Instead of splitting in a header and a data chunk, it is combining both parts. The header has fixed size entries, the data block has entries of size based on the corresponding header entry. The header also has fields that are only set on runtime which should be <tt>0</tt> in file and will be a pointer to the corresponding data upon loading.<br />
<br />
struct {<br />
/*0x00*/ uint32_t count;<br />
<br />
struct {<br />
/*0x00*/ uint32_t vertexCount; // Mostly 8<br />
/*0x04*/ char unk_04[0x4];<br />
/*0x08*/ uint64_t RUNTIME_08_ptr_data_0; // = &data[i].unk_0<br />
/*0x10*/ uint32_t count_10; // Mostly 6<br />
/*0x14*/ char unk_14[0x4];<br />
/*0x18*/ uint64_t RUNTIME_18_ptr_data_1; // = &data[i].unk_1<br />
/*0x20*/ uint64_t RUNTIME_20_ptr_data_2; // = &data[i].unk_2<br />
/*0x28*/ uint32_t nodeCount; // Mostly 24<br />
/*0x2c*/ char unk_2C[0x4];<br />
/*0x30*/ uint64_t RUNTIME_30_ptr_data_3; // = &data[i].unk_3<br />
/*0x38*/ float unk_38[6]; // not sure if floats: has e-08 values<br />
} header[count];<br />
<br />
struct { // NOTE: This is NOT a fixed size. Every entries' size depends on the header entry's value!<br />
vec3 vertices[header[i].vertexCount]; // The vertices that compose the convex hull mesh, this data is enough to generate the collision mesh<br />
struct {<br />
char unk_00[0x10]; // probably a vec4, but really e-07 values en masse<br />
} unk_1[header[i].count_10];<br />
char unk_2[header[i].count_10];<br />
struct {<br />
char unk; // 1 or -1<br />
char vertexIndex; // index in vertex list<br />
char unkIndex0; // index into the nodes<br />
char unkIndex1; // index into the nodes<br />
} nodes[header[i].nodeCount]; // a tree structure that connects the vertices together<br />
} data[count];<br />
} polytopeShapes;<br />
<br />
=joints=<br />
==JOIN==<br />
struct JOINEntry<br />
{<br />
unsigned int bodyAIdx;<br />
unsigned int bodyBIdx;<br />
char unk[4];<br />
enum<br />
{<br />
sphericalJoint = 0,<br />
shoulderJoint = 1,<br />
weldJoint = 2,<br />
#if version >= 2<br />
revoluteJoint = 3,<br />
prismaticJoint = 4,<br />
distanceJoint = 5,<br />
#endif<br />
};<br />
short jointType;<br />
short jointId; // reference into the corresponding chunk entries.<br />
} joints[];<br />
<br />
==WELJ, WLJ2==<br />
struct<br />
{<br />
/*0x00*/ mat3x4 frameA;<br />
/*0x30*/ mat3x4 frameB;<br />
/*0x60*/ float angularFrequencyHz;<br />
/*0x64*/ float angularDampingRatio;<br />
#if version >= 2 // WLJ2<br />
/*0x68*/ float linearFrequencyHz; // default 0<br />
/*0x6c*/ float linearDampingRatio; // default 0<br />
#endif<br />
} weldJoints[];<br />
<br />
==SPHJ==<br />
struct SPHJEntry<br />
{<br />
vec3 anchorA;<br />
vec3 anchorB;<br />
float frictionTorque;<br />
} sphericalJointEntries[];<br />
<br />
==SHOJ==<br />
Note that even though this chunk is handled differently since version 2, it does not have a version 2* chunk name.<br />
struct SHOJEntry<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerTwistAngle;<br />
float upperTwistAngle;<br />
float coneAngle;<br />
#if version >= 2<br />
char _x6c[8]; // NO BACKWARDS COMPATIBILITY as of {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.0.1.20979}} and {{Template:Sandbox/PrettyVersion|expansionlevel=7|build=7.3.0.24931}}! client always assumes new size!<br />
#endif<br />
} shoulderJoints[];<br />
<br />
==PRSJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerLimit;<br />
float upperLimit;<br />
float unk0;<br />
float unk1;<br />
float unk2;<br />
float unk3;<br />
} prismaticJoints[];<br />
<br />
==REVJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
mat3x4 frameA;<br />
mat3x4 frameB;<br />
float lowerAngle;<br />
float upperAngle;<br />
float unk0;<br />
uint32 unk1;<br />
} revoluteJoints[];<br />
<br />
==DSTJ (version 2+)==<br />
{{Template:SectionBox/VersionRange|min_expansionlevel=7|min_build=7.0.1.20979}}<br />
struct<br />
{<br />
char _unk[0x1c];<br />
} distanceJoints[];<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28303MDX2019-11-29T04:07:57Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#CORN|CORN]] popcornFxEmitters;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if !WC3Reforged // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] numberOfUVBS; // Used to be texture coordinates in the original WC3, now just an int counting the number of UV layers<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 numberOfUVBS;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
#if WC3 Reforged<br />
MDLBONESECTION bones[]; // Reforged doesn't have a bone count, read till end of chunk<br />
#else<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
#end<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
===KATV===<br />
Visiblity track. Values are boolean floats of 0.0 and 1.0 <br />
struct KATV<br />
{<br />
char tag[4]; // KATV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
#if WC3 Reforged<br />
MDLATTACHMENTSECTIONREFORGED attachments[]; // Reforged doesn't have an attachment count, read till end of chunk<br />
#else<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
#end<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLATTACHMENTSECTIONREFORGED : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
[[#KATV|KATV]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
#if WC3Reforged<br />
MDLPARTICLEEMITTER2 emitters[]; // Reforged doesn't have a number of emitters, read till end of chunk<br />
#else<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
#end<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
#if !WC3Reforged<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
#end<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
#if !WC3Reforged<br />
float staticLongitude;<br />
#end<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
#if !WC3Reforged<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
#end<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
#if WC3Reforged<br />
PARTICLE_BLEND_MODE blendMode;<br />
#end<br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
#if !WC3Reforged<br />
PARTICLE_BLEND_MODE blendMode;<br />
#end<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
#if WC3Reforged<br />
int32_t squirts;<br />
#end<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
#if !WC3Reforged<br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
#end <br />
#if WC3Reforged<br />
[[#KP2V|KP2V]] visibilityKeys;<br />
#else<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
#end<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2V===<br />
Particle emitter 2 visibility track<br />
struct KP2V<br />
{<br />
char tag[4]; // KP2V<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility;<br />
};<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==CORN==<br />
Reforged uses a 3rd party particle emitter as well, named PopcornFX version 2. The chunk references the *.pkb (popcornfx baked files) as well as the attributes exposed in the pkb files.<br />
<br />
struct CORN<br />
{<br />
CORNEMITTER cornEmitter[]; // count is unknown, read to end.<br />
};<br />
<br />
struct CORNEMITTER<br />
{<br />
uint32 emitterSize; // including this int<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
C4Color colorMultiplier; // the color is multiplied with the overall color of the particles<br />
C4Color teamColor; // default is (1,1,1,0) turned off, changes the color of some of the particles of some emitters (e.g: Wisp)<br />
char filePath[260]; // the path to the .pkb file<br />
char popcornFlags[260]; // comma separated flags (e.g: "Always=on" for emitters that are always on and aren't activated by animation tracks)<br />
[[#KPPA|KPPA]] alphaMultiplier;<br />
[[#KPPC|KPPC]] colorMultiplier;<br />
[[#KPPE|KPPE]] emissionRateMultiplier;<br />
[[#KPPL|KPPL]] lifespanMultiplier;<br />
[[#KPPS|KPPS]] speedMultiplier;<br />
[[#KPPV|KPPV]] visibility; // on/off<br />
};<br />
<br />
===KPPA===<br />
Popcorn emitter alpha multiplier<br />
struct KPPA<br />
{<br />
char tag[4]; // KPPA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaMultiplier;<br />
};<br />
<br />
===KPPC===<br />
Popcorn emitter color multiplier<br />
struct KPPC<br />
{<br />
char tag[4]; // KPPC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<C3Color> colorMultiplier;<br />
};<br />
<br />
===KPPE===<br />
Popcorn emitter emission rate multiplier<br />
struct KPPE<br />
{<br />
char tag[4]; // KPPE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRateMultiplier;<br />
};<br />
<br />
===KPPL===<br />
Popcorn emitter lifespan multiplier<br />
struct KPPL<br />
{<br />
char tag[4]; // KPPL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> lifespanMultiplier;<br />
};<br />
<br />
===KPPS===<br />
Popcorn emitter speed multiplier<br />
struct KPPS<br />
{<br />
char tag[4]; // KPPS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speedMultiplier;<br />
};<br />
<br />
===KPPV===<br />
Popcorn emitter visibility<br />
struct KPPV<br />
{<br />
char tag[4]; // KPPV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility; // if 1 emitter is activated, if 0 emitter is deactivated<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28302MDX2019-11-29T04:04:58Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#CORN|CORN]] popcornFxEmitters;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if !WC3Reforged // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] numberOfUVBS; // Used to be texture coordinates in the original WC3, now just an int counting the number of UV layers<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 numberOfUVBS;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
#if WC3 Reforged<br />
MDLBONESECTION bones[]; // Reforged doesn't have a bone count, read till end of chunk<br />
#else<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
#end<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
===KATV===<br />
Visiblity track. Values are boolean floats of 0.0 and 1.0 <br />
struct KATV<br />
{<br />
char tag[4]; // KATV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
#if WC3 Reforged<br />
MDLATTACHMENTSECTIONREFORGED attachments[]; // Reforged doesn't have an attachment count, read till end of chunk<br />
#else<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
#end<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLATTACHMENTSECTIONREFORGED : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
[[#KATV|KATV]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
#if WC3Reforged<br />
MDLPARTICLEEMITTER2 emitters[]; // Reforged doesn't have a number of emitters, read till end of chunk<br />
#else<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
#end<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
#if !WC3Reforged<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
#end<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
#if !WC3Reforged<br />
float staticLongitude;<br />
#end<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
#if !WC3Reforged<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
#end<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
#if WC3Reforged<br />
PARTICLE_BLEND_MODE blendMode;<br />
#end<br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
#if !WC3Reforged<br />
PARTICLE_BLEND_MODE blendMode;<br />
#end<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
#if WC3Reforged<br />
int32_t squirts;<br />
#end<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
#if !WC3Reforged<br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
#end <br />
<br />
#if WC3Reforged<br />
[[#KP2V|KP2V]] visibilityKeys;<br />
#else<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
#end<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2V===<br />
Particle emitter 2 visibility track<br />
struct KP2V<br />
{<br />
char tag[4]; // KP2V<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility;<br />
};<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==CORN==<br />
Reforged uses a 3rd party particle emitter as well, named PopcornFX version 2. The chunk references the *.pkb (popcornfx baked files) as well as the attributes exposed in the pkb files.<br />
<br />
struct CORN<br />
{<br />
CORNEMITTER cornEmitter[]; // count is unknown, read to end.<br />
};<br />
<br />
struct CORNEMITTER<br />
{<br />
uint32 emitterSize; // including this int<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
C4Color colorMultiplier; // the color is multiplied with the overall color of the particles<br />
C4Color teamColor; // default is (1,1,1,0) turned off, changes the color of some of the particles of some emitters (e.g: Wisp)<br />
char filePath[260]; // the path to the .pkb file<br />
char popcornFlags[260]; // comma separated flags (e.g: "Always=on" for emitters that are always on and aren't activated by animation tracks)<br />
[[#KPPA|KPPA]] alphaMultiplier;<br />
[[#KPPC|KPPC]] colorMultiplier;<br />
[[#KPPE|KPPE]] emissionRateMultiplier;<br />
[[#KPPL|KPPL]] lifespanMultiplier;<br />
[[#KPPS|KPPS]] speedMultiplier;<br />
[[#KPPV|KPPV]] visibility; // on/off<br />
};<br />
<br />
===KPPA===<br />
Popcorn emitter alpha multiplier<br />
struct KPPA<br />
{<br />
char tag[4]; // KPPA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaMultiplier;<br />
};<br />
<br />
===KPPC===<br />
Popcorn emitter color multiplier<br />
struct KPPC<br />
{<br />
char tag[4]; // KPPC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<C3Color> colorMultiplier;<br />
};<br />
<br />
===KPPE===<br />
Popcorn emitter emission rate multiplier<br />
struct KPPE<br />
{<br />
char tag[4]; // KPPE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRateMultiplier;<br />
};<br />
<br />
===KPPL===<br />
Popcorn emitter lifespan multiplier<br />
struct KPPL<br />
{<br />
char tag[4]; // KPPL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> lifespanMultiplier;<br />
};<br />
<br />
===KPPS===<br />
Popcorn emitter speed multiplier<br />
struct KPPS<br />
{<br />
char tag[4]; // KPPS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speedMultiplier;<br />
};<br />
<br />
===KPPV===<br />
Popcorn emitter visibility<br />
struct KPPV<br />
{<br />
char tag[4]; // KPPV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility; // if 1 emitter is activated, if 0 emitter is deactivated<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28301MDX2019-11-29T03:47:12Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] numberOfUVBS; // Used to be texture coordinates in the original WC3, now just an int counting the number of UV layers<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 numberOfUVBS;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
#if WC3 Reforged<br />
MDLBONESECTION bones[]; // Reforged doesn't have a bone count, read till end of chunk<br />
#else<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
#end<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
===KATV===<br />
Visiblity track. Values are boolean floats of 0.0 and 1.0 <br />
struct KATV<br />
{<br />
char tag[4]; // KATV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
#if WC3 Reforged<br />
MDLATTACHMENTSECTIONREFORGED attachments[]; // Reforged doesn't have an attachment count, read till end of chunk<br />
#else<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
#end<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLATTACHMENTSECTIONREFORGED : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
[[#KATV|KATV]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==CORN==<br />
Reforged uses a 3rd party particle emitter as well, named PopcornFX version 2. The chunk references the *.pkb (popcornfx baked files) as well as the attributes exposed in the pkb files.<br />
<br />
struct CORN<br />
{<br />
CORNEMITTER cornEmitter[]; // count is unknown, read to end.<br />
};<br />
<br />
struct CORNEMITTER<br />
{<br />
uint32 emitterSize; // including this int<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
C4Color colorMultiplier; // the color is multiplied with the overall color of the particles<br />
C4Color teamColor; // default is (1,1,1,0) turned off, changes the color of some of the particles of some emitters (e.g: Wisp)<br />
char filePath[260]; // the path to the .pkb file<br />
char popcornFlags[260]; // comma separated flags (e.g: "Always=on" for emitters that are always on and aren't activated by animation tracks)<br />
[[#KPPA|KPPA]] alphaMultiplier;<br />
[[#KPPC|KPPC]] colorMultiplier;<br />
[[#KPPE|KPPE]] emissionRateMultiplier;<br />
[[#KPPL|KPPL]] lifespanMultiplier;<br />
[[#KPPS|KPPS]] speedMultiplier;<br />
[[#KPPV|KPPV]] visibility; // on/off<br />
};<br />
<br />
===KPPA===<br />
Popcorn emitter alpha multiplier<br />
struct KPPA<br />
{<br />
char tag[4]; // KPPA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaMultiplier;<br />
};<br />
<br />
===KPPC===<br />
Popcorn emitter color multiplier<br />
struct KPPC<br />
{<br />
char tag[4]; // KPPC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<C3Color> colorMultiplier;<br />
};<br />
<br />
===KPPE===<br />
Popcorn emitter emission rate multiplier<br />
struct KPPE<br />
{<br />
char tag[4]; // KPPE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRateMultiplier;<br />
};<br />
<br />
===KPPL===<br />
Popcorn emitter lifespan multiplier<br />
struct KPPL<br />
{<br />
char tag[4]; // KPPL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> lifespanMultiplier;<br />
};<br />
<br />
===KPPS===<br />
Popcorn emitter speed multiplier<br />
struct KPPS<br />
{<br />
char tag[4]; // KPPS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speedMultiplier;<br />
};<br />
<br />
===KPPV===<br />
Popcorn emitter visibility<br />
struct KPPV<br />
{<br />
char tag[4]; // KPPV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility; // if 1 emitter is activated, if 0 emitter is deactivated<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28300MDX2019-11-29T03:30:15Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] unk; // Used to be texture coordinates in the original WC3, now just an int (data moved to UVBS)<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 unk;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
#if WC3 Reforged<br />
MDLBONESECTION bones[]; // Reforged doesn't have a bone count, read till end of chunk<br />
#else<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
#end<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
===KATV===<br />
Visiblity track. Values are boolean floats of 0.0 and 1.0 <br />
struct KATV<br />
{<br />
char tag[4]; // KATV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
#if WC3 Reforged<br />
MDLATTACHMENTSECTIONREFORGED attachments[]; // Reforged doesn't have an attachment count, read till end of chunk<br />
#else<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
#end<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLATTACHMENTSECTIONREFORGED : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
[[#KATV|KATV]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==CORN==<br />
Reforged uses a 3rd party particle emitter as well, named PopcornFX version 2. The chunk references the *.pkb (popcornfx baked files) as well as the attributes exposed in the pkb files.<br />
<br />
struct CORN<br />
{<br />
CORNEMITTER cornEmitter[]; // count is unknown, read to end.<br />
};<br />
<br />
struct CORNEMITTER<br />
{<br />
uint32 emitterSize; // including this int<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
C4Color colorMultiplier; // the color is multiplied with the overall color of the particles<br />
C4Color teamColor; // default is (1,1,1,0) turned off, changes the color of some of the particles of some emitters (e.g: Wisp)<br />
char filePath[260]; // the path to the .pkb file<br />
char popcornFlags[260]; // comma separated flags (e.g: "Always=on" for emitters that are always on and aren't activated by animation tracks)<br />
[[#KPPA|KPPA]] alphaMultiplier;<br />
[[#KPPC|KPPC]] colorMultiplier;<br />
[[#KPPE|KPPE]] emissionRateMultiplier;<br />
[[#KPPL|KPPL]] lifespanMultiplier;<br />
[[#KPPS|KPPS]] speedMultiplier;<br />
[[#KPPV|KPPV]] visibility; // on/off<br />
};<br />
<br />
===KPPA===<br />
Popcorn emitter alpha multiplier<br />
struct KPPA<br />
{<br />
char tag[4]; // KPPA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaMultiplier;<br />
};<br />
<br />
===KPPC===<br />
Popcorn emitter color multiplier<br />
struct KPPC<br />
{<br />
char tag[4]; // KPPC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<C3Color> colorMultiplier;<br />
};<br />
<br />
===KPPE===<br />
Popcorn emitter emission rate multiplier<br />
struct KPPE<br />
{<br />
char tag[4]; // KPPE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRateMultiplier;<br />
};<br />
<br />
===KPPL===<br />
Popcorn emitter lifespan multiplier<br />
struct KPPL<br />
{<br />
char tag[4]; // KPPL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> lifespanMultiplier;<br />
};<br />
<br />
===KPPS===<br />
Popcorn emitter speed multiplier<br />
struct KPPS<br />
{<br />
char tag[4]; // KPPS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speedMultiplier;<br />
};<br />
<br />
===KPPV===<br />
Popcorn emitter visibility<br />
struct KPPV<br />
{<br />
char tag[4]; // KPPV<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibility; // if 1 emitter is activated, if 0 emitter is deactivated<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28299MDX2019-11-29T02:48:44Z<p>Zee: /* BONE */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] unk; // Used to be texture coordinates in the original WC3, now just an int (data moved to UVBS)<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 unk;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
#if WC3 Reforged<br />
MDLBONESECTION bones[]; // Reforged doesn't have a bone count, read till end of chunk<br />
#else<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
#end<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28298MDX2019-11-29T02:38:09Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===GEOS (Reforged)===<br />
Reforged doesn't have a number of geosets, instead we read SUBMESHes till end of chunk.<br />
struct GEOS<br />
{<br />
SUBMESH submeshes[];<br />
};<br />
<br />
struct SUBMESH<br />
{<br />
int32 submeshSize; // including current int<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#PTYP|PTYP]] types;<br />
[[#PVTX|PVTX]] vertices;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#TANG|TANG]] tangents;<br />
[[#SKIN|SKIN]] boneWeights;<br />
[[#UVAS|UVAS]] unk; // Used to be texture coordinates in the original WC3, now just an int (data moved to UVBS)<br />
[[#UVBS|UVBS]] texCoords; // Chunk can be present twice when the mesh uses two uv layers UV0, UV1<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 unk;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===TANG===<br />
Vertex tangents<br />
struct TANG<br />
{<br />
char tag[4]; // TANG<br />
uint32_t count;<br />
C4Vector tangents[count];<br />
};<br />
===SKIN===<br />
Vertex weights. Divide weight values by 255f to normalize.<br />
struct SKIN<br />
{<br />
char tag[4]; // SKIN<br />
uint32_t count;<br />
BONEWEIGHT weights[count];<br />
};<br />
struct BONEWEIGHT<br />
{<br />
byte boneIndex0;<br />
byte boneIndex1;<br />
byte boneIndex2;<br />
byte boneIndex3;<br />
byte weight0;<br />
byte weight1;<br />
byte weight2;<br />
byte weight3;<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28297MDX2019-11-29T02:11:45Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 unk;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===UVBS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVBS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count];<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28296MDX2019-11-29T02:10:31Z<p>Zee: /* UVAS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
#if WC3 Reforged<br />
int32 unk;<br />
#else<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
#endif<br />
};<br />
<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28295MDX2019-11-29T02:07:09Z<p>Zee: /* TEXS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
#if WC3 Reforged<br />
char texturePath[268]; // string followed by 0's till 268<br />
#else<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
#endif<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28294MDX2019-11-29T01:52:37Z<p>Zee: </p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===MTLS(Reforged)===<br />
In reforged we don't know the number of materials, and sizeof(MTLS) isn't constant so read till end of the chunk.<br />
<br />
struct {<br />
SHADERREF shaderRef; // reference to the shader<br />
LAYS lays; // layers<br />
} MTLS;<br />
<br />
struct {<br />
int32 sizeOfLayer; // including the int<br />
int32 priorityPlane;<br />
int32 flags;<br />
char shaderName[80]; // shader name<br />
} SHADERREF;<br />
<br />
// Layers<br />
// 0 - Diffuse<br />
// 1 - Normal Map<br />
// 2 - ORM = Occlusion, Roughness, Metalic (RGB)<br />
// 3 - Emissive<br />
// 4 - Team Color<br />
// 5 - Environment Map<br />
struct {<br />
char LAYS[4];<br />
int32 numberOfLAYS;<br />
TEXTURELAYER textureLayers[numberOfLAYS]<br />
} LAYS;<br />
<br />
struct {<br />
int32 textureLayerSize; // including this int<br />
MDLTEXOP blendMode;<br />
MDLGEO shadingFlags;<br />
int32 textureID;<br />
int32 textureAnimationID;<br />
int32 coordID;<br />
float alpha;<br />
float emissiveGain;<br />
#if version == 1000<br />
float unk1[3];<br />
int32 unk2[2]; // 0 filled<br />
#endif<br />
[[#KMTE|KMTE]] emissiveKeys;<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] textureLayerKeys;<br />
} TEXTURELAYER;<br />
<br />
===KMTE===<br />
Material alpha track<br />
struct KMTE<br />
{<br />
char tag[4]; // KMTE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissiveKeys;<br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28293MDX2019-11-29T01:19:16Z<p>Zee: /* SEQS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
#if WC3 Reforged<br />
float rarity;<br />
int syncPoint; // probably for syncing audio with the animation, only seen 0 so far<br />
#endif<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
#if !WC3 Reforged<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
#endif<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28292MDX2019-11-29T01:07:49Z<p>Zee: /* MODL */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 && version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28291MDX2019-11-29T01:07:35Z<p>Zee: /* MODL */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900 || version != 1000 // Reforged doesn't have those flags<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=28290MDX2019-11-29T01:03:30Z<p>Zee: /* VERS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
The WC3 and WC3 Reforged structure is documented [https://www.hiveworkshop.com/threads/mdx-specifications.240487 here].<br />
<br />
uint32_t version; // 800 WC3, 900, 1000 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_NO_FALLBACK = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX (duplicated tag name, client doesn't validate them)<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint64_t unused; // explicitly 0, ignored by client<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveType; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint16_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint16_t minVertex; <br />
uint16_t maxVertex;<br />
uint16_t unused; // explicitly 0, ignored by client<br />
<br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files however use of both is supported<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=27758MDX2019-10-18T01:54:06Z<p>Zee: /* MODL */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
uint32_t version; // 800 WC3, 900 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_UNKNOWN_1500 = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint32_t unknown1; // always 0 filled<br />
uint32_t unknown2; // always 0 filled<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveTypes[geoset.primitiveTypesCount]; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint32_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint32_t maxVertex; // the largest vertex in the list <br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=27757MDX2019-10-18T01:52:51Z<p>Zee: /* MODL */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
uint32_t version; // 800 WC3, 900 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds; // for reforged: seems to be the radius first then the box <br />
uint32_t blendTime;<br />
#if version != 900<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
#endif<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_UNKNOWN_1500 = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint32_t unknown1; // always 0 filled<br />
uint32_t unknown2; // always 0 filled<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveTypes[geoset.primitiveTypesCount]; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint32_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint32_t maxVertex; // the largest vertex in the list <br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=27756MDX2019-10-18T00:01:13Z<p>Zee: /* VERS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
uint32_t version; // 800 WC3, 900 WC3 Reforged, 1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t blendTime;<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_UNKNOWN_1500 = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint32_t unknown1; // always 0 filled<br />
uint32_t unknown2; // always 0 filled<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveTypes[geoset.primitiveTypesCount]; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint32_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint32_t maxVertex; // the largest vertex in the list <br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=MDX&diff=27755MDX2019-10-18T00:00:19Z<p>Zee: /* VERS */</p>
<hr />
<div>{{Template:SectionBox/VersionRange|max_expansionlevel=1|max_exclusive=1}}<br />
[[MDX|MDX]] files are [[chunk|chunked]] binary files that contain model objects. They are the predecessor of the [[M2]] format.<br />
<br />
First used in Warcraft 3, [[MDX|MDX]] was actively developed and used in WoW as the primary model format until patch {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.11.0.3925}}. Although obsolete, some [[DBC|DBC]]s still contain filenames with a <tt>.mdx</tt> extension.<br />
<br />
<b>Note:</b> The majority of the below information has been taken from the {{Template:Sandbox/PrettyVersion|expansionlevel=0|build=0.5.3.3368}} client and is only truly compliant for version 1300 of the format.<br />
<br />
__TOC__<br />
<br />
==Structure==<br />
The complete structure for a [[MDX|MDX]] file. <b>Note:</b> Chunks after <tt>MODL</tt> don't have to conform to a specific order and can be unimplemented on a per-file basis - particularly the <tt>K***</tt> sub-chunks.<br />
struct MDLBASE<br />
{<br />
char magic[4]; // MDLX<br />
[[#VERS|VERS]] version;<br />
[[#MODL|MODL]] model; <br />
[[#SEQS|SEQS]] sequences;<br />
[[#GLBS|GLBS]] globalSeqs;<br />
[[#MTLS|MTLS]] materials;<br />
[[#TEXS|TEXS]] textures;<br />
[[#TXAN|TXAN]] textureanims;<br />
[[#GEOS|GEOS]] geosets;<br />
[[#GEOA|GEOA]] geosetAnims;<br />
[[#BONE|BONE]] bones;<br />
[[#LITE|LITE]] lights;<br />
[[#HELP|HELP]] helpers;<br />
[[#ATCH|ATCH]] attachments;<br />
[[#PIVT|PIVT]] pivotPoints;<br />
[[#PREM|PREM]] particleEmitters;<br />
[[#CAMS|CAMS]] cameras;<br />
[[#EVTS|EVTS]] events;<br />
[[#PRE2|PRE2]] particleEmitters2;<br />
[[#HTST|HTST]] hitTestShapes;<br />
[[#RIBB|RIBB]] ribbonEmitters;<br />
[[#CLID|CLID]] collision;<br />
};<br />
<br />
<br />
==Common Types==<br />
<br />
===C3Color===<br />
struct C3Color<br />
{<br />
float b;<br />
float g;<br />
float r;<br />
};<br />
<br />
===C4QuaternionCompressed===<br />
For the WoW variant of [[MDX|MDX]], all {{Template:Type|C4Quaternion}}s are packed in int64_ts.<br />
<syntaxhighlight lang="cpp"><br />
struct C4QuaternionCompressed<br />
{<br />
int64_t m_data;<br />
<br />
C4Quaternion Get()<br />
{<br />
C4Quaternion result; <br />
result.X = ( m_data >> 42 ) * 0.00000047683716;<br />
result.Y = (( m_data << 22 ) >> 43 ) * 0.00000095367432;<br />
result.Z = ( m_data & 0x1FFFFF ) * 0.00000095367432;<br />
result.W = GetW( result.X, result.Y, result.Z ); <br />
return result;<br />
}<br />
<br />
double GetW(float x, float y, float z)<br />
{<br />
double len = x * x + y * y + z * z; <br />
if (( 1.0 - len ) >= 0.00000095367432 )<br />
return sqrt( 1.0 - len );<br />
<br />
return 0.0;<br />
}<br />
<br />
void Set(C4Quaternion source)<br />
{<br />
int32_t sign = ( source->W >= 0.0 ? 1 : -1 ); <br />
int64_t x = (int64_t)( sign * source->X * 2097152.0 ) << 21;<br />
int64_t y = (int64_t)( sign * source->Y * 1048576.0 ) & 0x1FFFFF;<br />
int64_t z = (int64_t)( sign * source->Z * 1048576.0 ) & 0x1FFFFF;<br />
<br />
m_data = z | (( y | x ) << 21 );<br />
} <br />
};<br />
</syntaxhighlight><br />
<br />
===CMdlBounds===<br />
struct CMdlBounds<br />
{<br />
{{Template:Type|CAaBox}} extent;<br />
float radius;<br />
};<br />
<br />
===MDLKEYTRACK===<br />
The <tt>MDLKEYTRACK</tt> is a simpler equivalent of the [[M2#Types|M2Track]]. It stores a list of <tt>MDLKEYFRAMES</tt> which are essentially tuples of a time and T type values.<br />
If the type is <tt>TRACK_HERMITE</tt> or <tt>TRACK_BEZIER</tt> then the frame also contains <tt>inTan</tt> and <tt>outTan</tt> information. See the relevant [[M2#Interpolation|M2 interpolation section]] for more information.<br />
<br />
Under certain conditions the client overrides the track type e.g. when <tt>MDLMODELSECTION.flags & 4</tt> (always animate) is set <tt>TRACK_LINEAR</tt> is used.<br />
<br />
template<typename T><br />
struct MDLKEYTRACK<T><br />
{<br />
uint32_t count;<br />
MDLTRACKTYPE type;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
MDLKEYFRAME<T> keys[count]; <br />
};<br />
<br />
template<typename T><br />
struct MDLKEYFRAME<T><br />
{<br />
int32_t time;<br />
T value;<br />
#if MDLKEYTRACK.type > TRACK_LINEAR<br />
T inTan;<br />
T outTan;<br />
#endif<br />
};<br />
<br />
enum MDLTRACKTYPE : uint32_t<br />
{<br />
TRACK_NO_INTERP = 0x0,<br />
TRACK_LINEAR = 0x1,<br />
TRACK_HERMITE = 0x2,<br />
TRACK_BEZIER = 0x3,<br />
NUM_TRACK_TYPES = 0x4,<br />
};<br />
<br />
===MDLSIMPLEKEYTRACK===<br />
The <tt>MDLSIMPLEKEYTRACK</tt> is used in place of the <tt>MDLKEYTRACK</tt> when only linear integer values are required. Types used by this track are <tt>MDLINTKEY</tt> and <tt>MDLEVENTKEY</tt>.<br />
template<typename T><br />
struct MDLSIMPLEKEYTRACK<T><br />
{<br />
uint32_t count;<br />
uint32_t globalSeqId; // [[#GLBS|GLBS]] index or 0xFFFFFFFF if none<br />
T keys[count];<br />
};<br />
<br />
struct MDLINTKEY // default type<br />
{<br />
uint32_t time;<br />
uint32_t value;<br />
};<br />
<br />
struct MDLEVENTKEY // only used for the [[#EVTS|EVTS]] [[#KEVT|KEVT]] sub-chunk<br />
{<br />
int32_t time;<br />
};<br />
<br />
===MDLGENOBJECT===<br />
<tt>MDLGENOBJECT</tt> is a base class inherited by several chunks. This is not just for common data but is also used to build an object hierarchy.<br />
<br />
The hierarchy is usually organised as: <code>Bones (root bones first) → Lights → Helpers → Attachments → ParticleEmitters → RibbonEmitters → Events → HitTestShapes</code>. The client will throw an exception if the objectIds are not sequential.<br />
struct MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
char name[0x50];<br />
uint32_t objectId; // globally unique id, used as the index in the hierarchy. index into [[#PIVT|PIVT]]<br />
uint32_t parentId; // parent MDLGENOBJECT's objectId or 0xFFFFFFFF if none<br />
uint32_t flags;<br />
<br />
[[#KGTR|KGTR]] transkeys;<br />
[[#KGRT|KGRT]] rotkeys;<br />
[[#KGSC|KGSC]] scalekeys;<br />
};<br />
<br />
====Flags====<br />
<b>Notes:</b> Certain flag combinations are invalid and will throw exceptions. Flags ≥ 0x20000 are only applicable to [[#PRE2|PRE2]] objects. GENOBJECT flags are also set in the class constructor.<br />
{| class="wikitable"<br />
|-<br />
! width="50" | Flag<br />
! width="450" | Meaning<br />
! width="650" | Notes<br />
|-<br />
| 0x00000001 || DONT_INHERIT_TRANSLATION || <br />
|- || || <br />
| 0x00000002 || DONT_INHERIT_SCALING || <br />
|- || || <br />
| 0x00000004 || DONT_INHERIT_ROTATION || <br />
|- || || <br />
| 0x00000008 || BILLBOARD || <br />
|- || || <br />
| 0x00000010 || BILLBOARD_LOCK_X || <br />
|- || || <br />
| 0x00000020 || BILLBOARD_LOCK_Y || <br />
|- || || <br />
| 0x00000040 || BILLBOARD_LOCK_Z || <br />
|- || || <br />
| 0x00000080 || GENOBJECT_MDLBONESECTION || not explicitly set in the files however all other GENOBJECT flags are<br />
|- || || <br />
| 0x00000100 || GENOBJECT_MDLLIGHTSECTION || <br />
|- || || <br />
| 0x00000200 || GENOBJECT_MDLEVENTSECTION || <br />
|- || || <br />
| 0x00000400 || GENOBJECT_MDLATTACHMENTSECTION || <br />
|- || || <br />
| 0x00000800 || GENOBJECT_MDLPARTICLEEMITTER2 || <br />
|- || || <br />
| 0x00001000 || GENOBJECT_MDLHITTESTSHAPE || <br />
|- || || <br />
| 0x00002000 || GENOBJECT_MDLRIBBONEMITTER || <br />
|- || || <br />
| 0x00004000 || PROJECT || <br />
|- || || <br />
| 0x00008000 || EMITTER_USES_TGA ([[#PREM|PREM]]), UNSHADED ([[#PRE2|PRE2]]) || UNSHADED disables lighting on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00010000 || EMITTER_USES_MDL ([[#PREM|PREM]]), SORT_PRIMITIVES_FAR_Z ([[#PRE2|PRE2]]) || <br />
|- || || <br />
| 0x00020000 || LINE_EMITTER || <br />
|- || || <br />
| 0x00040000 || PARTICLE_UNFOGGED || disables fog on [[M2/Rendering#CParticleMat|particle materials]]<br />
|- || || <br />
| 0x00080000 || PARTICLE_USE_MODEL_SPACE || uses model space instead of world space<br />
|- || || <br />
| 0x00100000 || PARTICLE_INHERIT_SCALE || <br />
|- || || <br />
| 0x00200000 || PARTICLE_INSTANT_VELOCITY_LIN || ivelScale is applied, see [[#PRE2|PRE2]]<br />
|- || || <br />
| 0x00400000 || PARTICLE_0XKILL || particles are destroyed after their first update tick<br />
|- || || <br />
| 0x00800000 || PARTICLE_Z_VELOCITY_ONLY || particle X and Y velocities are set to 0.0 at instantiation<br />
|- || || <br />
| 0x01000000 || PARTICLE_TUMBLER || {{Template:Unverified|unused by the client}}<br />
|- || || <br />
| 0x02000000 || PARTICLE_TAIL_GROWS || <br />
|- || || <br />
| 0x04000000 || PARTICLE_EXTRUDE || extrudes between the previous and current translation<br />
|- || || <br />
| 0x08000000 || PARTICLE_XYQUADS || particles align to the XY axis facing the Z axis<br />
|- || || <br />
| 0x10000000 || PARTICLE_PROJECT || <br />
|- || || <br />
| 0x20000000 || PARTICLE_FOLLOW || particles follow each other<br />
|- || || <br />
|}<br />
<br />
====KGTR====<br />
Geoset translation track<br />
struct KGTR<br />
{<br />
char tag[4]; // KGTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
====KGRT====<br />
Geoset rotation track<br />
struct KGRT<br />
{<br />
char tag[4]; // KGRT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
====KGSC====<br />
Geoset scale track<br />
struct KGSC<br />
{<br />
char tag[4]; // KGSC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==VERS==<br />
Version. Equivalent to the <tt>MVER</tt> chunk.<br />
<br />
File analysis of v1400 shows no structural differences to v1300, the only apparent change is that referenced file paths are now normalized.<br />
<br />
v1500 sees two structural changes from the previous iterations namely; new flags in the [[#MTLS|MTLS]] chunk and a complete redesign of the [[#GEOS_.28v1500.29|GEOS]] chunk.<br />
<br />
uint32_t version; // 800 WC3, 900 WC3 Reforged,1300 {{Template:Sandbox/VersionRange|max_expansionlevel=0|max_build=0.9.1.3810|max_exclusive=1}}, 1400 & 1500 {{Template:Sandbox/VersionRange|min_expansionlevel=0|min_build=0.9.1.3810}}<br />
<br />
==MODL==<br />
Global model information.<br />
struct MDLMODELSECTION<br />
{<br />
char name[0x50];<br />
char animationFile[0x104]; // always 0 filled<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t blendTime;<br />
uint8_t flags; // deprecated, always 0. &1, 2: GROUND_TRACK, &4: always animate<br />
};<br />
<br />
enum GROUND_TRACK<br />
{<br />
TRACK_YAW_ONLY = 0x0,<br />
TRACK_PITCH_YAW = 0x1,<br />
TRACK_PITCH_YAW_ROLL = 0x2,<br />
GROUND_TRACK_MASK = 0x3,<br />
};<br />
<br />
==SEQS==<br />
Sequences. [[MDX|MDX]] uses a single track for all animations meaning start times and end times between each animation are consecutive.<br />
struct SEQS<br />
{<br />
uint32_t numSeqs; // limited to 0xFF<br />
MDLSEQUENCESSECTION sequences [numSeqs];<br />
};<br />
<br />
struct MDLSEQUENCESSECTION<br />
{<br />
char name[0x50];<br />
{{Template:Type|CiRange}} time; // start time, end time<br />
float movespeed; // movement speed of the entity while playing this animation<br />
uint32_t flags; // &1: non looping<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
float frequency; // determines chance of this animation playing. for all animations of the same type this must add to 1.0<br />
{{Template:Type|CiRange}} replay; // the client will pick a random number of repetitions within bounds<br />
uint32_t blendTime;<br />
};<br />
<br />
==GLBS==<br />
Maximum lengths for sequence ranges. This chunk has no count, the client reads uint32_ts until chunk.size bytes have been read.<br />
struct MDLGLOBALSEQSECTION<br />
{<br />
uint32_t length[chunk.size / 0x4];<br />
};<br />
<br />
==MTLS==<br />
Materials.<br />
struct MTLS<br />
{<br />
uint32_t numMaterials; // limited to 0xFF<br />
uint32_t unused; // has values but is ignored by the client<br />
MDLMATERIALSECTION materials[numMaterials];<br />
};<br />
<br />
struct MDLMATERIALSECTION<br />
{<br />
uint32_t size;<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t numLayers; <br />
MDLTEXLAYER texLayers[numLayers]; <br />
};<br />
<br />
struct MDLTEXLAYER<br />
{ <br />
uint32_t size;<br />
MDLTEXOP blendMode;<br />
MDLGEO flags;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF for none<br />
uint32_t transformId; // [[#TXAN|TXAN]] index or 0xFFFFFFFF for none<br />
int32_t coordId; // [[#UAVS|UAVS]] index or -1 for none, defines vertex buffer format <code>coordId == -1 ? GxVBF_PN : GxVBF_PNT0</code><br />
float staticAlpha; // 0 for transparent, 1 for opaque<br />
<br />
[[#KMTA|KMTA]] alphaKeys;<br />
[[#KMTF|KMTF]] flipKeys;<br />
};<br />
<br />
enum MDLTEXOP : uint32_t<br />
{<br />
TEXOP_LOAD = 0x0,<br />
TEXOP_TRANSPARENT = 0x1,<br />
TEXOP_BLEND = 0x2,<br />
TEXOP_ADD = 0x3,<br />
TEXOP_ADD_ALPHA = 0x4,<br />
TEXOP_MODULATE = 0x5,<br />
TEXOP_MODULATE2X = 0x6,<br />
NUMTEXOPS = 0x7,<br />
};<br />
<br />
enum MDLGEO : uint32_t<br />
{<br />
MODEL_GEO_UNSHADED = 0x1,<br />
MODEL_GEO_SPHERE_ENV_MAP = 0x2, // unused until v1500<br />
MODEL_GEO_WRAPWIDTH = 0x4, // unused until v1500<br />
MODEL_GEO_WRAPHEIGHT = 0x8, // unused until v1500<br />
MODEL_GEO_TWOSIDED = 0x10,<br />
MODEL_GEO_UNFOGGED = 0x20,<br />
MODEL_GEO_NO_DEPTH_TEST = 0x40,<br />
MODEL_GEO_NO_DEPTH_SET = 0x80,<br />
MODEL_GEO_UNKNOWN_1500 = 0x100, // added in v1500. seen in <tt>ElwynnTallWaterfall01.mdx</tt>, <tt>FelwoodTallWaterfall01.mdx</tt> and <tt>LavaFallsBlackRock*.mdx</tt><br />
};<br />
<br />
===KMTA===<br />
Material alpha track<br />
struct KMTA<br />
{<br />
char tag[4]; // KMTA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KMTF===<br />
Material flipbook texture track<br />
struct KMTF<br />
{<br />
char tag[4]; // KMTF<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> flipKeys;<br />
};<br />
<br />
==TEXS==<br />
Textures. The client reads <tt>MDLTEXTURESECTION</tt>s until chunk.size bytes have been read.<br />
struct TEXS<br />
{<br />
MDLTEXTURESECTION textures[chunk.size / sizeof(MDLTEXTURESECTION)];<br />
};<br />
<br />
struct MDLTEXTURESECTION<br />
{<br />
REPLACEABLE_MATERIAL_IDS replaceableId; // used for texture variations or 0 for none<br />
char image[0x104]; // 0 filled when replaceableId is set<br />
uint32_t flags; // &1: wrap width, &2: wrap height<br />
};<br />
<br />
enum REPLACEABLE_MATERIAL_IDS : uint32_t<br />
{<br />
TEX_COMPONENT_SKIN = 0x1,<br />
TEX_COMPONENT_OBJECT_SKIN = 0x2,<br />
TEX_COMPONENT_WEAPON_BLADE = 0x3,<br />
TEX_COMPONENT_WEAPON_HANDLE = 0x4,<br />
TEX_COMPONENT_ENVIRONMENT = 0x5,<br />
TEX_COMPONENT_CHAR_HAIR = 0x6,<br />
TEX_COMPONENT_CHAR_FACIAL_HAIR = 0x7,<br />
TEX_COMPONENT_SKIN_EXTRA = 0x8,<br />
TEX_COMPONENT_UI_SKIN = 0x9,<br />
TEX_COMPONENT_TAUREN_MANE = 0xA,<br />
TEX_COMPONENT_MONSTER_1 = 0xB,<br />
TEX_COMPONENT_MONSTER_2 = 0xC,<br />
TEX_COMPONENT_MONSTER_3 = 0xD,<br />
TEX_COMPONENT_ITEM_ICON = 0xE,<br />
NUM_REPLACEABLE_MATERIAL_IDS = 0xF,<br />
};<br />
<br />
==TXAN==<br />
Texture Animations.<br />
struct TXAN<br />
{<br />
uint32_t numTexAnims;<br />
MDLTEXANIMSECTION textureAnims[numTexAnims];<br />
};<br />
<br />
struct MDLTEXANIMSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#KTAT|KTAT]] transkeys;<br />
[[#KTAR|KTAR]] rotkeys;<br />
[[#KTAS|KTAS]] scalekeys;<br />
};<br />
<br />
===KTAT===<br />
Texture animation translation track<br />
struct KTAT<br />
{<br />
char tag[4]; // KTAT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KTAR===<br />
Texture animation rotation track<br />
struct KTAR<br />
{<br />
char tag[4]; // KTAR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C4QuaternionCompressed|C4QuaternionCompressed]]> rotkeys;<br />
};<br />
===KTAS===<br />
Texture animation scale track<br />
struct KTAS<br />
{<br />
char tag[4]; // KTAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> scalekeys;<br />
};<br />
<br />
==GEOS==<br />
Geosets.<br />
<br />
===GEOS (≤ v1400)===<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t size;<br />
<br />
[[#VRTX|VRTX]] vertices;<br />
[[#NRMS|NRMS]] normals;<br />
[[#UAVS|UAVS]] texCoords;<br />
MDLPRIMITIVES primitives;<br />
[[#GNDX|GNDX]] vertGroupIndices;<br />
[[#MTGC|MTGC]] groupMatrixCounts;<br />
[[#MATS|MATS]] matrices;<br />
[[#BIDX|BIDX]] boneIndices;<br />
[[#BWGT|BWGT]] boneWeights;<br />
<br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
uint32_t selectionGroup; // when formatted as four digits, the first two digits map to CHARACTER_GEOSET_SECTIONS, the second two digits are an associated sub-group<br />
// see the related [[M2/.skin#Mesh_part_ID|M2 skin section]] for more information<br />
uint32_t flags; // &1: unselectable<br />
[[#CMdlBounds|CMdlBounds]] bounds;<br />
uint32_t numSeqBounds;<br />
[[#CMdlBounds|CMdlBounds]] seqBounds[numSeqBounds];<br />
};<br />
<br />
struct MDLPRIMITIVES<br />
{<br />
[[#PTYP|PTYP]] types;<br />
[[#PCNT|PCNT]] counts;<br />
[[#PVTX|PVTX]] vertices;<br />
};<br />
<br />
enum CHARACTER_GEOSET_SECTIONS<br />
{<br />
CHARGEOSET_HAIR = 0x0,<br />
CHARGEOSET_BEARD = 0x1,<br />
CHARGEOSET_SIDEBURN = 0x2,<br />
CHARGEOSET_MOUSTACHE = 0x3,<br />
CHARGEOSET_GLOVE = 0x4,<br />
CHARGEOSET_BOOT = 0x5,<br />
CHARGEOSET_OBSOLETEDONTUSEME = 0x6,<br />
CHARGEOSET_EAR = 0x7,<br />
CHARGEOSET_SLEEVES = 0x8,<br />
CHARGEOSET_PANTS = 0x9,<br />
CHARGEOSET_DOUBLET = 0xA,<br />
CHARGEOSET_PANTDOUBLET = 0xB,<br />
CHARGEOSET_TABARD = 0xC,<br />
CHARGEOSET_ROBE = 0xD,<br />
CHARGEOSET_LOINCLOTH = 0xE,<br />
NUM_CHARGEOSETS = 0xF,<br />
CHARGEOSET_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
===GEOS (v1500)===<br />
{{Template:SectionBox|This section only applies to version 1500.}}<br />
struct GEOS<br />
{<br />
uint32_t numGeosets; // limited to 0xFF<br />
MDLGEOSETSECTION geosets[numGeosets];<br />
MDLBATCH batches[numGeosets];<br />
};<br />
<br />
struct MDLGEOSETSECTION<br />
{<br />
uint32_t materialId;<br />
{{Template:Type|C3Vector}} boundsCentre;<br />
float boundsRadius;<br />
uint32_t selectionGroup;<br />
uint32_t geosetIndex;<br />
uint32_t flags; // &1: unselectable, &0x10: project2D, &0x20: shaderSkin, other flags are unimplemented<br />
<br />
char vertexTag[4]; // PVTX<br />
uint32_t vertexCount;<br />
char primTypeTag[4]; // PTYP<br />
uint32_t primitiveTypesCount;<br />
char primVertexTag[4]; // PVTX<br />
uint32_t primitiveVerticesCount;<br />
<br />
uint32_t unknown1; // always 0 filled<br />
uint32_t unknown2; // always 0 filled<br />
}<br />
<br />
struct MDLBATCH<br />
{<br />
const MDLGEOSETSECTION geoset = GEOS.geosets[index]; // GEOS geoset of matching index<br />
<br />
MDLVERTEX vertices[geoset.vertexCount];<br />
uint32_t primitiveTypes[geoset.primitiveTypesCount]; // always 0x3 (Triangle)<br />
uint32_t unknown; // always 0<br />
<br />
uint32_t numPrimVertices; // matches geoset.primitiveVerticesCount<br />
uint32_t maxVertex; // the largest vertex in the list <br />
uint16_t primitiveVertices[numPrimVertices]; <br />
<br />
#if numPrimVertices % 8 != 0<br />
uint16_t padding[x]; // alignment padding, calculated as <code>x = (8 - numPrimVertices % 8)</code><br />
#endif<br />
}<br />
<br />
struct MDLVERTEX // same structure as [[M2#Vertices|M2Vertex]]<br />
{<br />
{{Template:Type|C3Vector}} position;<br />
uint8_t boneWeights[4];<br />
uint8_t boneIndices[4];<br />
{{Template:Type|C3Vector}} normal;<br />
{{Template:Type|C2Vector}} texCoords[2]; // second is always (0,0) in all beta files<br />
}<br />
<br />
===VRTX===<br />
Vertices. Also used by [[#CLID|CLID]].<br />
struct VRTX<br />
{<br />
char tag[4]; // VRTX<br />
uint32_t count; // limited to 0xFFFF<br />
{{Template:Type|C3Vector}} vertices[count]; <br />
};<br />
===NRMS===<br />
Normals. Also used by [[#CLID|CLID]].<br />
struct NRMS<br />
{<br />
char tag[4]; // NRMS<br />
uint32_t count;<br />
{{Template:Type|C3Vector}} normals[count];<br />
};<br />
===UVAS===<br />
Texture coordinates. The client uses UVAS.count * [[#VRTX|VRTX]].count to calculate how many C2Vectors to read<br />
struct UVAS<br />
{<br />
char tag[4]; // UVAS<br />
uint32_t count;<br />
{{Template:Type|C2Vector}} texCoords[count * vertices.count];<br />
};<br />
===PTYP===<br />
Primitive types. This is always 0x4 (Triangle) although the client appears to support all <tt>FACETYPE</tt>s<br />
struct PTYP<br />
{<br />
char tag[4]; // PTYP<br />
uint32_t count;<br />
FACETYPE primitiveTypes[count];<br />
};<br />
<br />
enum FACETYPE : uint8_t<br />
{<br />
FACETYPE_POINTS = 0x0,<br />
FACETYPE_LINES = 0x1,<br />
FACETYPE_LINE_LOOP = 0x2,<br />
FACETYPE_LINE_STRIP = 0x3,<br />
FACETYPE_TRIANGLES = 0x4,<br />
FACETYPE_TRIANGLE_STRIP = 0x5,<br />
FACETYPE_TRIANGLE_FAN = 0x6,<br />
FACETYPE_QUADS = 0x7,<br />
FACETYPE_QUAD_STRIP = 0x8,<br />
FACETYPE_POLYGON = 0x9<br />
};<br />
===PCNT===<br />
Primitive counts. The number of uint16_ts used by [[#PVTX|PVTX]] in each group<br />
struct PCNT<br />
{<br />
char tag[4]; // PCNT<br />
uint32_t count;<br />
uint32_t primitiveCounts[count];<br />
};<br />
===PVTX===<br />
Primitive vertices<br />
struct PVTX<br />
{<br />
char tag[4]; // PVTX<br />
uint32_t count;<br />
uint16_t primitiveVertices[count];<br />
};<br />
===GNDX===<br />
Vertex group indices<br />
struct GNDX<br />
{<br />
char tag[4]; // GNDX<br />
uint32_t count;<br />
uint8_t vertGroupIndices[count];<br />
};<br />
===MTGC===<br />
Group matrix counts<br />
struct MTGC<br />
{<br />
char tag[4]; // MTGC<br />
uint32_t count;<br />
uint32_t groupMatrixCounts[count];<br />
};<br />
===MATS===<br />
Matrices<br />
struct MATS<br />
{<br />
char tag[4]; // MATS<br />
uint32_t count;<br />
uint32_t matrices[count];<br />
};<br />
===BIDX===<br />
Bone indices<br />
struct BIDX<br />
{<br />
char tag[4]; // BIDX<br />
uint32_t count;<br />
uint32_t boneIndices[count];<br />
};<br />
===BWGT===<br />
Bone weights<br />
struct BWGT<br />
{<br />
char tag[4]; // BWGT<br />
uint32_t count;<br />
uint32_t boneWeights[count];<br />
};<br />
<br />
==GEOA==<br />
Geoset animations<br />
struct GEOA<br />
{<br />
uint32_t numGeoAnims;<br />
MDLGEOSETANIMSECTION geosetAnims[numGeoAnims];<br />
};<br />
<br />
struct MDLGEOSETANIMSECTION<br />
{<br />
uint32_t size;<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
uint32_t flags; // &1: color<br />
<br />
[[#KGAO|KGAO]] alphaKeys;<br />
[[#KGAC|KGAC]] colorKeys;<br />
};<br />
<br />
===KGAO===<br />
Animated geoset alpha track<br />
struct KGAO<br />
{<br />
char tag[4]; // KGAO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KGAC===<br />
Animated geoset color track<br />
struct KGAC<br />
{<br />
char tag[4]; // KGAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
<br />
==BONE==<br />
Bones<br />
struct BONE<br />
{<br />
uint32_t numBones;<br />
MDLBONESECTION bones[numBones];<br />
};<br />
<br />
struct MDLBONESECTION : MDLGENOBJECT<br />
{<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t geosetId; // [[#GEOS|GEOS]] index or 0xFFFFFFFF if none<br />
uint32_t geosetAnimId; // [[#GEOA|GEOA]] index or 0xFFFFFFFF if none<br />
};<br />
<br />
==LITE==<br />
Lights.<br />
struct LITE<br />
{<br />
uint32_t numLights;<br />
MDLLIGHTSECTION lights[numLights];<br />
};<br />
<br />
struct MDLLIGHTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
LIGHT_TYPE type;<br />
float staticAttenStart;<br />
float staticAttenEnd;<br />
[[#C3Color|C3Color]] staticColor;<br />
float staticIntensity; <br />
[[#C3Color|C3Color]] staticAmbColor;<br />
float staticAmbIntensity;<br />
<br />
[[#KLAS|KLAS]] attenstartkeys;<br />
[[#KLAE|KLAE]] attenendkeys;<br />
[[#KLAC|KLAC]] colorkeys;<br />
[[#KLAI|KLAI]] intensitykeys;<br />
[[#KLBC|KLBC]] ambcolorkeys;<br />
[[#KLBI|KLBI]] ambintensitykeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum LIGHT_TYPE : uint32_t<br />
{<br />
LIGHTTYPE_OMNI = 0x0,<br />
LIGHTTYPE_DIRECT = 0x1,<br />
LIGHTTYPE_AMBIENT = 0x2,<br />
NUM_MDL_LIGHT_TYPES = 0x3,<br />
};<br />
<br />
===KLAS===<br />
Light attenuation start track<br />
struct KLAS<br />
{<br />
char tag[4]; // KLAS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenstartkeys;<br />
};<br />
===KLAE===<br />
Light attenuation end track<br />
struct KLAE<br />
{<br />
char tag[4]; // KLAE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> attenendkeys;<br />
};<br />
===KLAC===<br />
Light color track<br />
struct KLAC<br />
{<br />
char tag[4]; // KLAC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorkeys;<br />
};<br />
===KLAI===<br />
Light intensity track<br />
struct KLAI<br />
{<br />
char tag[4]; // KLAI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> intensitykeys;<br />
};<br />
===KLBC===<br />
Light ambience color track<br />
struct KLBC<br />
{<br />
char tag[4]; // KLBC<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> ambcolorkeys<br />
};<br />
===KLBI===<br />
Light ambient intensity track<br />
struct KLBI<br />
{<br />
char tag[4]; // KLBI<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> ambintensitykeys;<br />
};<br />
===KVIS===<br />
Visiblity track. <b>Note:</b> Unlike other tracks this one is used globally. Values are boolean floats of 0.0 and 1.0 <br />
struct KVIS<br />
{<br />
char tag[4]; // KVIS<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> visibilityKeys;<br />
};<br />
<br />
==HELP==<br />
Helpers.<br />
struct HELP<br />
{<br />
uint32_t count;<br />
MDLGENOBJECT helpers[count];<br />
};<br />
<br />
==ATCH==<br />
Attachment Points.<br />
struct ATCH<br />
{<br />
uint32_t numAttachments;<br />
uint32_t unused; // has values but is ignored by the client<br />
<br />
MDLATTACHMENTSECTION attachments[numAttachments];<br />
};<br />
<br />
struct MDLATTACHMENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object; // MDLGENOBJECT.name is loosely the GEOCOMPONENTLINKS enum<br />
<br />
GEOCOMPONENTLINKS attachmentId;<br />
uint8_t padding;<br />
char path[0x104]; // 0 filled in all alpha files<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
enum GEOCOMPONENTLINKS : uint32_t<br />
{<br />
ATTACH_SHIELD = 0x0,<br />
ATTACH_HANDR = 0x1,<br />
ATTACH_HANDL = 0x2,<br />
ATTACH_ELBOWR = 0x3,<br />
ATTACH_ELBOWL = 0x4,<br />
ATTACH_SHOULDERR = 0x5,<br />
ATTACH_SHOULDERL = 0x6,<br />
ATTACH_KNEER = 0x7,<br />
ATTACH_KNEEL = 0x8,<br />
ATTACH_HIPR = 0x9,<br />
ATTACH_HIPL = 0xA,<br />
ATTACH_HELM = 0xB,<br />
ATTACH_BACK = 0xC,<br />
ATTACH_SHOULDERFLAPR = 0xD,<br />
ATTACH_SHOULDERFLAPL = 0xE,<br />
ATTACH_TORSOBLOODFRONT = 0xF,<br />
ATTACH_TORSOBLOODBACK = 0x10,<br />
ATTACH_BREATH = 0x11,<br />
ATTACH_PLAYERNAME = 0x12,<br />
ATTACH_UNITEFFECT_BASE = 0x13,<br />
ATTACH_UNITEFFECT_HEAD = 0x14,<br />
ATTACH_UNITEFFECT_SPELLLEFTHAND = 0x15,<br />
ATTACH_UNITEFFECT_SPELLRIGHTHAND = 0x16,<br />
ATTACH_UNITEFFECT_SPECIAL1 = 0x17,<br />
ATTACH_UNITEFFECT_SPECIAL2 = 0x18,<br />
ATTACH_UNITEFFECT_SPECIAL3 = 0x19,<br />
ATTACH_SHEATH_MAINHAND = 0x1A,<br />
ATTACH_SHEATH_OFFHAND = 0x1B,<br />
ATTACH_SHEATH_SHIELD = 0x1C,<br />
ATTACH_PLAYERNAMEMOUNTED = 0x1D,<br />
ATTACH_LARGEWEAPONLEFT = 0x1E,<br />
ATTACH_LARGEWEAPONRIGHT = 0x1F,<br />
ATTACH_HIPWEAPONLEFT = 0x20,<br />
ATTACH_HIPWEAPONRIGHT = 0x21,<br />
ATTACH_TORSOSPELL = 0x22,<br />
ATTACH_HANDARROW = 0x23,<br />
NUM_ATTACH_SLOTS = 0x24,<br />
ATTACH_NONE = 0xFFFFFFFF,<br />
};<br />
<br />
==PIVT==<br />
Pivot points. The client reads C3Vectors until chunk.size bytes have been read. PivotPoints are paired with <tt>MDLGENOBJECT</tt>s by matching indices.<br />
struct PIVT<br />
{<br />
{{Template:Type|C3Vector}} pivotPoints[chunk.size / 0xC]; <br />
};<br />
<br />
==PREM==<br />
Particle emitters. <b>Note:</b> This is deprecated use [[#PRE2|PRE2]] instead.<br />
struct PREM<br />
{ <br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
float staticEmissionRate; <br />
float staticGravity; <br />
float staticLongitude;<br />
float staticLatitude;<br />
MDLPARTICLE particle;<br />
<br />
[[#KPEE|KPEE]] emissionRate;<br />
[[#KPEG|KPEG]] gravity;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KPLT|KPLT]] latitude;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
struct MDLPARTICLE<br />
{<br />
char path[0x104]; // model path<br />
float staticLife;<br />
float staticSpeed;<br />
<br />
[[#KPEL|KPEL]] life;<br />
[[#KPES|KPES]] speed;<br />
};<br />
<br />
===KPEE===<br />
Particle emitter emission rate track<br />
struct KPEE<br />
{<br />
char tag[4]; // KPEE<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate;<br />
};<br />
===KPEG===<br />
Particle emitter particle gravity track<br />
struct KPEG<br />
{<br />
char tag[4]; // KPEG<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KPLT===<br />
Particle emitter particle latitude track<br />
struct KPLT<br />
{<br />
char tag[4]; // KPLT<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPEL===<br />
Particle emitter particle life track<br />
struct KPEL<br />
{<br />
char tag[4]; // KPEL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KPES===<br />
Particle emitter particle speed track<br />
struct KPES<br />
{<br />
char tag[4]; // KPES<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
<br />
==CAMS==<br />
Cameras.<br />
struct CAMS<br />
{<br />
uint32_t numCameras;<br />
MDLCAMERASECTION cameras[numCameras]; <br />
};<br />
<br />
struct MDLCAMERASECTION<br />
{<br />
uint32_t size;<br />
char name[0x50]; // common names are CameraPortrait, Portrait and Paperdoll<br />
{{Template:Type|C3Vector}} pivot;<br />
float fieldOfView; // default is 0.9500215<br />
float farClip; // default is 27.7777786<br />
float nearClip; // default is 0.222222224<br />
{{Template:Type|C3Vector}} targetPivot;<br />
<br />
[[#KCTR|KCTR]] transkeys;<br />
[[#KCRL|KCRL]] rollkeys;<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KTTR|KTTR]] targettranskeys;<br />
};<br />
<br />
===KCTR===<br />
Camera translation track<br />
struct KCTR<br />
{<br />
char tag[4]; // KCTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> transkeys;<br />
};<br />
===KCRL===<br />
Camera roll track<br />
struct KCRL<br />
{<br />
char tag[4]; // KCRL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> rollkeys;<br />
};<br />
===KTTR===<br />
Camera target translation track<br />
struct KTTR<br />
{<br />
char tag[4]; // KTTR<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<{{Template:Type|C3Vector}}> targettranskeys;<br />
};<br />
<br />
==EVTS==<br />
Events. For a complete list see the [[M2#Possible_Events|M2 events section]].<br />
struct EVTS<br />
{<br />
uint32_t numEventObjs;<br />
MDLEVENTSECTION events[numEventObjs];<br />
};<br />
<br />
struct MDLEVENTSECTION : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
[[#KEVT|KEVT]] eventKeys;<br />
};<br />
<br />
===KEVT===<br />
Event time track<br />
struct KEVT<br />
{<br />
char tag[4]; // KEVT<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLEVENTKEY> eventKeys;<br />
};<br />
<br />
==PRE2==<br />
Particle Emitter 2, the successor of the [[#PREM|PREM]] chunk.<br />
struct PRE2<br />
{<br />
uint32_t numEmitters;<br />
MDLPARTICLEEMITTER2 emitters[numEmitters];<br />
};<br />
<br />
struct MDLPARTICLEEMITTER2 : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
PARTICLE_EMITTER_TYPE emitterType;<br />
float staticSpeed; // particleVelocity<br />
float staticVariation; // particleVelocityVariation, velocity multiplier. client adds 1.0 and multiplies by random multiplier<br />
float staticLatitude;<br />
float staticLongitude;<br />
float staticGravity; // particleAcceleration, only applied to the z axis<br />
float staticZsource; // deducted from the particle starting z position. must be ≥ 0.0<br />
float staticLife; // base particle lifespan in seconds<br />
float staticEmissionRate; // base amount of particles per second. client treats negatives as 0.0<br />
float staticLength; // height, for <tt>PET_SPLINE endAngle (multiplied by emissionRate)</tt>, for <tt>PET_SPHERE outerRadius</tt><br />
float staticWidth; // width, for <tt>PET_SPLINE startAngle</tt>, for <tt>PET_SPHERE innerRadius</tt><br />
uint32_t rows;<br />
uint32_t cols;<br />
PARTICLE_TYPE type; <br />
float tailLength;<br />
float middleTime;<br />
[[#C3Color|C3Color]] startColor;<br />
[[#C3Color|C3Color]] middleColor;<br />
[[#C3Color|C3Color]] endColor;<br />
uint8_t startAlpha;<br />
uint8_t middleAlpha;<br />
uint8_t endAlpha; <br />
float startScale;<br />
float middleScale;<br />
float endScale;<br />
uint32_t lifespanUVAnimStart;<br />
uint32_t lifespanUVAnimEnd;<br />
uint32_t lifespanUVAnimRepeat;<br />
uint32_t decayUVAnimStart;<br />
uint32_t decayUVAnimEnd;<br />
uint32_t decayUVAnimRepeat;<br />
uint32_t tailUVAnimStart;<br />
uint32_t tailUVAnimEnd;<br />
uint32_t tailUVAnimRepeat;<br />
uint32_t tailDecayUVAnimStart;<br />
uint32_t tailDecayUVAnimEnd;<br />
uint32_t tailDecayUVAnimRepeat;<br />
PARTICLE_BLEND_MODE blendMode;<br />
uint32_t textureId; // [[#TEXS|TEXS]] index or 0xFFFFFFFF if none<br />
int32_t priorityPlane; // priority is sorted lowest to highest<br />
uint32_t replaceableId; // only seen in <tt>Wisp.mdx</tt><br />
char geometryMdl[0x104]; // particle model<br />
char recursionMdl[0x104]; <br />
float twinkleFPS; // default is 10.0<br />
float twinkleOnOff; // boolean, twinkle applies additional scaling to make a shrink and grow effect<br />
float twinkleScaleMin; // twinkle is not applied if <code>twinkleScaleMax - twinkleScaleMin == 0.0</code><br />
float twinkleScaleMax;<br />
float ivelScale; // instant velocity scale, multiplier for each particle's intial velocity<br />
float tumblexMin; // tumble adds a randomised rotation to each particle<br />
float tumblexMax;<br />
float tumbleyMin;<br />
float tumbleyMax;<br />
float tumblezMin;<br />
float tumblezMax;<br />
float drag; // decreases particle velocity over time<br />
float spin;<br />
{{Template:Type|C3Vector}} windVector; // simulates being blown<br />
float windTime; // how long windVector is to be applied<br />
float followSpeed1;<br />
float followScale1;<br />
float followSpeed2;<br />
float followScale2;<br />
uint32_t numSplines;<br />
{{Template:Type|C3Vector}} spline[numSplines];<br />
uint32_t squirts; // boolean<br />
<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
[[#KP2S|KP2S]] speed;<br />
[[#KP2R|KP2R]] variation;<br />
[[#KP2L|KP2L]] latitude;<br />
[[#KPLN|KPLN]] longitude;<br />
[[#KP2G|KP2G]] gravity;<br />
[[#KLIF|KLIF]] life;<br />
[[#KP2E|KP2E]] emissionRate;<br />
[[#KP2W|KP2W]] width;<br />
[[#KP2N|KP2N]] length;<br />
[[#KP2Z|KP2Z]] zsource;<br />
};<br />
<br />
enum PARTICLE_BLEND_MODE : uint32_t<br />
{<br />
PBM_BLEND = 0x0,<br />
PBM_ADD = 0x1,<br />
PBM_MODULATE = 0x2,<br />
PBM_MODULATE_2X = 0x3,<br />
PBM_ALPHA_KEY = 0x4,<br />
NUM_PARTICLE_BLEND_MODES = 0x5,<br />
};<br />
<br />
enum PARTICLE_TYPE : uint32_t<br />
{<br />
PT_HEAD = 0x0,<br />
PT_TAIL = 0x1,<br />
PT_BOTH = 0x2,<br />
NUM_PARTICLE_TYPES = 0x3,<br />
};<br />
<br />
enum PARTICLE_EMITTER_TYPE : uint32_t<br />
{<br />
PET_BASE = 0x0,<br />
PET_PLANE = 0x1,<br />
PET_SPHERE = 0x2,<br />
PET_SPLINE = 0x3,<br />
NUM_PARTICLE_EMITTER_TYPES = 0x4,<br />
};<br />
<br />
===KP2S===<br />
Particle emitter 2 speed track<br />
struct KP2S<br />
{<br />
char tag[4]; // KP2S<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> speed;<br />
};<br />
===KP2R===<br />
Particle emitter 2 variation track<br />
struct KP2R<br />
{<br />
char tag[4]; // KP2R<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> variation;<br />
};<br />
===KP2L===<br />
Particle emitter 2 latitude track<br />
struct KP2L<br />
{<br />
char tag[4]; // KP2L<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> latitude;<br />
};<br />
===KPLN===<br />
Particle emitter 2 longitude track<br />
struct KPLN<br />
{<br />
char tag[4]; // KPLN<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> longitude;<br />
};<br />
===KP2G===<br />
Particle emitter 2 gravity track<br />
struct KP2G<br />
{<br />
char tag[4]; // KP2G<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> gravity;<br />
};<br />
===KLIF===<br />
Particle emitter 2 life track<br />
struct KLIF<br />
{<br />
char tag[4]; // KLIF<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> life;<br />
};<br />
===KP2E===<br />
Particle emitter 2 emission rate track<br />
struct KP2E<br />
{<br />
char tag[4]; // KP2E<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> emissionRate; <br />
};<br />
===KP2W===<br />
Particle emitter 2 width track<br />
struct KP2W<br />
{<br />
char tag[4]; // KP2W<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> width;<br />
};<br />
===KP2N===<br />
Particle emitter 2 length track<br />
struct KP2N<br />
{<br />
char tag[4]; // KP2N<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> length;<br />
};<br />
===KP2Z===<br />
Particle emitter 2 zsource track<br />
struct KP2Z<br />
{<br />
char tag[4]; // KP2Z<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> zsource;<br />
};<br />
<br />
==HTST==<br />
Hit test shapes.<br />
struct HTST<br />
{<br />
uint32_t numHitTestShapes;<br />
MDLHITTESTSHAPE hittestshapes[numHitTestShapes];<br />
};<br />
<br />
struct MDLHITTESTSHAPE : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
GEOM_SHAPE type;<br />
<br />
#if type == SHAPE_BOX:<br />
MDLBOX box;<br />
#elseif type == SHAPE_CYLINDER:<br />
MDLCYLINDER cylinder;<br />
#elseif type == SHAPE_SPHERE:<br />
MDLSPHERE sphere;<br />
#elseif type == SHAPE_PLANE:<br />
MDLPLANE plane;<br />
#endif<br />
};<br />
<br />
enum GEOM_SHAPE : uint8_t<br />
{<br />
SHAPE_BOX = 0x0,<br />
SHAPE_CYLINDER = 0x1,<br />
SHAPE_SPHERE = 0x2,<br />
SHAPE_PLANE = 0x3,<br />
NUM_SHAPES = 0x4,<br />
};<br />
<br />
struct MDLBOX<br />
{<br />
{{Template:Type|C3Vector}} minimum;<br />
{{Template:Type|C3Vector}} maximum;<br />
};<br />
<br />
struct MDLCYLINDER<br />
{<br />
{{Template:Type|C3Vector}} base;<br />
float height;<br />
float radius;<br />
};<br />
<br />
struct MDLSPHERE<br />
{<br />
{{Template:Type|C3Vector}} center;<br />
float radius;<br />
};<br />
<br />
struct MDLPLANE<br />
{<br />
float length;<br />
float width;<br />
};<br />
<br />
==RIBB==<br />
Ribbon emitter.<br />
struct RIBB<br />
{<br />
uint32_t numEmitters;<br />
MDLRIBBONEMITTER emitters[numEmitters];<br />
};<br />
<br />
struct MDLRIBBONEMITTER : MDLGENOBJECT<br />
{<br />
uint32_t size;<br />
[[#MDLGENOBJECT|MDLGENOBJECT]] object;<br />
<br />
uint32_t emitterSize;<br />
float staticHeightAbove; // must be ≥ 0.0<br />
float staticHeightBelow; // must be ≥ 0.0<br />
float staticAlpha; // 0 is transparent, 1 is opaque<br />
[[#C3Color|C3Color]] staticColor;<br />
float edgeLifetime; // in seconds. must be > 0.0, client forces a minimum of 0.25s<br />
uint32_t staticTextureSlot;<br />
uint32_t edgesPerSecond; // must be ≥ 1.0<br />
uint32_t textureRows;<br />
uint32_t textureCols; <br />
uint32_t materialId; // [[#MTLS|MTLS]] index<br />
float gravity; <br />
<br />
[[#KRHA|KRHA]] heightAbove;<br />
[[#KRHB|KRHB]] heightBelow;<br />
[[#KRAL|KRAL]] alphaKeys;<br />
[[#KRCO|KRCO]] colorKeys;<br />
[[#KRTX|KRTX]] textureSlot; // unused by alpha files<br />
[[#KVIS|KVIS]] visibilityKeys;<br />
};<br />
<br />
===KRHA===<br />
Ribbon emitter height above track<br />
struct KRHA<br />
{<br />
char tag[4]; // KRHA<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightAbove;<br />
};<br />
===KRHB===<br />
Ribbon emitter height below track<br />
struct KRHB<br />
{<br />
char tag[4]; // KRHB<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> heightBelow;<br />
};<br />
===KRAL===<br />
Ribbon emitter alpha track<br />
struct KRAL<br />
{<br />
char tag[4]; // KRAL<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<float> alphaKeys;<br />
};<br />
===KRCO===<br />
Ribbon emitter color track<br />
struct KRCO<br />
{<br />
char tag[4]; // KRCO<br />
[[#MDLKEYTRACK|MDLKEYTRACK]]<[[#C3Color|C3Color]]> colorKeys;<br />
};<br />
===KRTX===<br />
Ribbon emitter texture slot track<br />
struct KRTX<br />
{<br />
char tag[4]; // KRTX<br />
[[#MDLSIMPLEKEYTRACK|MDLSIMPLEKEYTRACK]]<MDLINTKEY> textureSlot;<br />
};<br />
<br />
==CLID==<br />
Collision.<br />
struct MDLCOLLISION<br />
{<br />
[[#VRTX|VRTX]] vertices;<br />
[[#TRI|TRI]] triIndices; <br />
[[#NRMS|NRMS]] facetNormals;<br />
};<br />
<br />
===TRI ===<br />
Triangles<br />
struct TRI<br />
{<br />
char tag[4]; // 'TRI ' the space (ASCII char 32) is intentional<br />
uint32_t count;<br />
uint16_t triIndices[count];<br />
};<br />
<br />
[[Category:Format]]</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit:BottomToolbar&diff=26433WoWEdit:BottomToolbar2018-11-04T23:01:26Z<p>Zee: </p>
<hr />
<div>The bottom toolbar consists mainly of toggle buttons.<br />
<br />
{| class="wikitable"<br />
|-<br />
|[[File:WoWEdit BottomToolbar 1.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 2.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 3.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 4.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 5.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 6.jpg]]<br />
|(ADT Chunk Grid Toggle)<br />
|[[https://i.imgur.com/qtoTzt8.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 7.jpg]]<br />
|(Area Share)<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 8.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 9.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 10.jpg]]<br />
|Show terrain vertex volor (disabling allows viewing terrain texture un-modified, etc)<br />
|[[https://i.imgur.com/jPIHTfg.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 11.jpg]]<br />
|Turn distance fog on/off<br />
|[[https://i.imgur.com/oaPjscq.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 12.jpg]]<br />
|Turn Active Doodads on/off<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 13.jpg]]<br />
|Turn Doodads on/off<br />
|[[https://i.imgur.com/772N91c.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 14.jpg]]<br />
|Show / Hide WMO ???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 15.jpg]]<br />
|Show Hide WMO Invisible Collision<br />
|[[https://i.imgur.com/qcertfQ.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 16.jpg]]<br />
|Turn Compass display on/off<br />
|[[https://i.imgur.com/NTHsDjL.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 17.jpg]]<br />
|Turn the display of Shadows on/off<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 18.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 19.jpg]]<br />
|(sunlight)<br />
|[[https://i.imgur.com/45Ti0qP.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 20.jpg]]<br />
|Show point/spot lights on/off<br />
|[[https://i.imgur.com/dRoiRmp.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 21.jpg]]<br />
|Show point/spot light markers on/off<br />
|[[https://i.imgur.com/pgmZa7V.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 22.jpg]]<br />
|Show Volume Fog Scene Objects on/off<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 23.jpg]]<br />
|Turn on/off display of Water<br />
|[[https://i.imgur.com/3uLCta3.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 24.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 25.jpg]]<br />
|View Sea Level<br />
|[[https://i.imgur.com/f7VlGOO.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 26.jpg]]<br />
|View Deep-Water Level<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 27.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 28.jpg]]<br />
|View Death Plane<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 29.jpg]]<br />
|(Display pathable terrain)<br />
|[[https://i.imgur.com/R5NVDjQ.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 30.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 31.jpg]]<br />
|(terrain wireframe overlay toggle)<br />
|[[https://i.imgur.com/T4Y1fUt.png Example]]<br />
|-<br />
|[[File:WoWEdit BottomToolbar 32.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 33.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 34.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 35.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 36.jpg]]<br />
|Enable Animations<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 37.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 38.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 39.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 40.jpg]]<br />
|(View terrain face normals)<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 41.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 42.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 43.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 44.jpg]]<br />
|???<br />
|<br />
|-<br />
|[[File:WoWEdit BottomToolbar 45.jpg]]<br />
|???<br />
|<br />
|-<br />
|}</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit&diff=26432WoWEdit2018-11-04T23:00:54Z<p>Zee: /* Analysis on WoWEdit */</p>
<hr />
<div><br />
== Analysis on WoWEdit ==<br />
<br />
[[File:WoWEditFullPreview.jpg|800px]]<br />
<br />
<br />
'''Toolbars'''<br />
<br />
Button names are taken from video reference, status bars when they are highlighted.<br />
Parentheses are used when the name of the button is (guess work / deduction)<br />
<br />
[[WoWEdit:TopToolbar]]<br />
<br />
[[WoWEdit:SideToolbar]]<br />
<br />
[[WoWEdit:BottomToolbar]]<br />
<br />
<br />
'''Tools'''<br />
<br />
[[WoWEdit: Terrain Tools]]<br />
<br />
[[WoWEdit: Texture Tools]]<br />
<br />
[[WoWEdit: Doodad Tools]]<br />
<br />
[[WoWEdit: Point / Spot Light List]]<br />
<br />
<br />
'''Menus'''<br />
<br />
[[WoWEdit:RightClickMenus]]</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit:_Texture_Tools&diff=26431WoWEdit: Texture Tools2018-11-04T22:51:00Z<p>Zee: </p>
<hr />
<div>The Texture Tools Window<br />
<br />
[[File:WoWEditTextureTools1.jpg]]<br />
<br />
<br />
Color picker panel for the vertex color<br />
<br />
[[File:WoWEditTextureTools2 VertexColor.jpg]]<br />
<br />
<br />
A preview of the texture brush on the terrain, showing the size and falloff<br />
<br />
[[File:WoWEditTextureBrush.jpg]]<br />
<br />
<br />
Holding alt will switch the brush to highlight nearby vertex colors and allow sampling their color<br />
<br />
[[File:WoWEditVertexColorSample.jpg]]</div>Zeehttps://wowdev.wiki/index.php?title=File:WoWEditVertexColorSample.jpg&diff=26430File:WoWEditVertexColorSample.jpg2018-11-04T22:48:39Z<p>Zee: WoWEdit Vertex Color Sampling brush</p>
<hr />
<div>WoWEdit Vertex Color Sampling brush</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit:_Texture_Tools&diff=26429WoWEdit: Texture Tools2018-11-04T22:47:23Z<p>Zee: </p>
<hr />
<div>[[File:WoWEditTextureTools1.jpg]]<br />
[[File:WoWEditTextureTools2 VertexColor.jpg]]<br />
<br />
A preview of the texture brush on the terrain, showing the size and falloff<br />
<br />
[[File:WoWEditTextureBrush.jpg]]</div>Zeehttps://wowdev.wiki/index.php?title=File:WoWEditTextureBrush.jpg&diff=26428File:WoWEditTextureBrush.jpg2018-11-04T22:46:30Z<p>Zee: WoWEdit Texture Brush</p>
<hr />
<div>WoWEdit Texture Brush</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit:_Doodad_Tools&diff=26427WoWEdit: Doodad Tools2018-11-04T22:44:02Z<p>Zee: </p>
<hr />
<div>(higher resolution unavailable)<br />
<br />
[[File:WoWEditDoodadTools.PNG]]</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit:_Doodad_Tools&diff=26426WoWEdit: Doodad Tools2018-11-04T22:43:50Z<p>Zee: Created page with "(higher resolution unavailable) File:WoWEditDoodadTools.PNG"</p>
<hr />
<div>(higher resolution unavailable)<br />
[[File:WoWEditDoodadTools.PNG]]</div>Zeehttps://wowdev.wiki/index.php?title=File:WoWEditDoodadTools.PNG&diff=26425File:WoWEditDoodadTools.PNG2018-11-04T22:43:28Z<p>Zee: Previews of the Doodad Tools in WoWEdit</p>
<hr />
<div>Previews of the Doodad Tools in WoWEdit</div>Zeehttps://wowdev.wiki/index.php?title=WoWEdit&diff=26424WoWEdit2018-11-04T22:41:40Z<p>Zee: </p>
<hr />
<div><br />
== Analysis on WoWEdit ==<br />
<br />
[[File:WoWEditFullPreview.jpg|800px]]<br />
<br />
<br />
[[WoWEdit:TopToolbar]]<br />
<br />
[[WoWEdit:SideToolbar]]<br />
<br />
[[WoWEdit:BottomToolbar]]<br />
<br />
[[WoWEdit: Terrain Tools]]<br />
<br />
[[WoWEdit: Texture Tools]]<br />
<br />
[[WoWEdit: Doodad Tools]]<br />
<br />
[[WoWEdit: Point / Spot Light List]]<br />
<br />
[[WoWEdit:RightClickMenus]]</div>Zee