This article's documentation is for anything that uses the Source engine. Click here for more information.

MDL: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Adding skin table ifnormation)
 
(35 intermediate revisions by 15 users not shown)
Line 1: Line 1:
{{LanguageBar}}
{{TabsBar|main=MDL}}
'''MDL''' is the extension for [[Source]]'s proprietary model format. It defines the structure of the model along with animation, bounding box, hit box, materials, mesh and [[LOD]] information. It does not, however, contain ''all'' the information needed for the model.  Additional data is stored in [[PHY]], [[ANI]], [[VTX]] and [[VVD]] files, and sometimes, usually for shared animations, other .mdl files.
'''MDL''' is the extension for [[Source]]'s proprietary model format. It defines the structure of the model along with animation, bounding box, hit box, materials, mesh and [[LOD]] information. It does not, however, contain ''all'' the information needed for the model.  Additional data is stored in [[PHY]], [[ANI]], [[VTX]] and [[VVD]] files, and sometimes, usually for shared animations, other .mdl files.


== File format ==
== File format ==
Some details of the file format may be gleaned from the source code in Valve's <code>studio.h</code>, specifically the struct <code>studiohdr_t</code>. The early header defines a series of offsets and lengths for various sub-sections within the file, along with some key scalar information. The MDL also contains the names of materials ([[VMT]]), which may be used and referenced in various ways.
Some details of the file format may be gleaned from the source code in Valve's <code>studio.h</code>, specifically the struct <code>studiohdr_t</code>. The early header defines a series of offsets and lengths for various sub-sections within the file, along with some key scalar information. The MDL also contains the names of materials ([[VMT]]), which may be used and referenced in various ways.  
 
{{TODO|Someone who knows C, fix up the header declarations}}


=== Main header ===
=== Main header ===
 
To get the latest header for specific game, please use the <code>studio.h</code> file in the Valve's SDK instead.
<source lang="cpp">struct studiohdr_t
<source lang="cpp">struct studiohdr_t
{
{
int id; // Model format ID, such as "IDST" (0x49 0x44 0x53 0x54)
    int         id;             // Model format ID, such as "IDST" (0x49 0x44 0x53 0x54)
int version; // Format version number, such as 48 (0x30,0x00,0x00,0x00)
    int         version;       // Format version number, such as 48 (0x30,0x00,0x00,0x00)
char[64] name; // The internal name of the model, padding with null bytes.
    int        checksum;      // This has to be the same in the phy and vtx files to load!
// Typically "my_model.mdl" will have an internal name of "my_model"
    char       name[64];       // The internal name of the model, padding with null bytes.
int dataLength; // ??
                                // Typically "my_model.mdl" will have an internal name of "my_model"
    int         dataLength;     // Data size of MDL file in bytes.
   
   
// A vector is 12 bytes, three 4-byte float-values in a row.
    // A vector is 12 bytes, three 4-byte float-values in a row.
Vector eyeposition; // Position of player viewpoint relative to model origin
    Vector     eyeposition;   // Position of player viewpoint relative to model origin
Vector illumposition; // ?? Presumably the point used for lighting when per-vertex lighting is not enabled.
    Vector     illumposition; // Position (relative to model origin) used to calculate ambient light contribution and cubemap reflections for the entire model.
Vector hull_min; // Corner of model hull box with the least X/Y/Z values
    Vector     hull_min;       // Corner of model hull box with the least X/Y/Z values
Vector hull_max; // Opposite corner of model hull box
    Vector     hull_max;       // Opposite corner of model hull box
Vector   view_bbmin;
    Vector     view_bbmin;     // Same, but for bounding box,
Vector view_bbmax;
    Vector     view_bbmax;     // which is used for view culling
   
   
int flags; // Binary flags in little-endian order.  
    int         flags;         // Binary flags in little-endian order.  
// ex (00000001,00000000,00000000,11000000) means flags for position 0, 30, and 31 are set.  
                                // ex (0x010000C0) means flags for position 0, 30, and 31 are set.  
// Set model flags section for more information
                                // Set model flags section for more information
   
   
/*
    /*
* After this point, the header contains many references to offsets
    * After this point, the header contains many references to offsets
* within the MDL file and the number of items at those offsets.
    * within the MDL file and the number of items at those offsets.
*
    *
* Offsets are from the very beginning of the file.
    * Offsets are from the very beginning of the file.
*  
    *  
* Note that indexes/counts are not always paired and ordered consistently.
    * Note that indexes/counts are not always paired and ordered consistently.
*/
    */  
   
   
// mstudiobone_t
    // mstudiobone_t
int bone_count; // Number of data sections (of type mstudiobone_t)
    int       bone_count;   // Number of data sections (of type mstudiobone_t)
int bone_offset; // Offset of first data section
    int       bone_offset;   // Offset of first data section
   
   
// mstudiobonecontroller_t
    // mstudiobonecontroller_t
int bonecontroller_count;
    int       bonecontroller_count;
int bonecontroller_offset;
    int       bonecontroller_offset;
   
   
// mstudiohitboxset_t
    // mstudiohitboxset_t
int hitbox_count;
    int       hitbox_count;
int hitbox_offset;
    int       hitbox_offset;
   
   
// mstudioanimdesc_t
    // mstudioanimdesc_t
int localanim_count;
    int       localanim_count;
int localanim_offset;
    int       localanim_offset;
   
   
// mstudioseqdesc_t
    // mstudioseqdesc_t
int localseq_count;
    int       localseq_count;
int localseq_offset;
    int       localseq_offset;
   
   
int activitylistversion; // ??
    int       activitylistversion; // ??
int eventsindexed; // ??
    int       eventsindexed;       // ??
   
   
// VMT texture filenames
    // VMT texture filenames
// mstudiotexture_t
    // mstudiotexture_t
int texture_count;
    int       texture_count;
int texture_offset;
    int       texture_offset;
   
   
// This offset points to a series of ints.
    // This offset points to a series of ints.
        // Each int value, in turn, is an offset relative to the start of this header/the-file,
    // Each int value, in turn, is an offset relative to the start of this header/the-file,
        // At which there is a null-terminated string.
    // At which there is a null-terminated string.
int texturedir_count;
    int       texturedir_count;
int texturedir_offset;
    int       texturedir_offset;
   
   
// Each skin-family assigns a texture-id to a skin location
    // Each skin-family assigns a texture-id to a skin location
int skinreference_count;
    int       skinreference_count;
int skinrfamily_count;
    int       skinrfamily_count;
int             skinreference_index;
    int       skinreference_index;
   
   
// mstudiobodyparts_t
    // mstudiobodyparts_t
int bodypart_count;
    int       bodypart_count;
int bodypart_offset;
    int       bodypart_offset;
   
   
        // Local attachment points
    // Local attachment points      
// mstudioattachment_t
    // mstudioattachment_t
int attachment_count;
    int       attachment_count;
int attachment_offset;
    int       attachment_offset;
   
   
// Node values appear to be single bytes, while their names are null-terminated strings.
    // Node values appear to be single bytes, while their names are null-terminated strings.
int localnode_count;
    int       localnode_count;
int localnode_index;
    int       localnode_index;
int localnode_name_index;
    int       localnode_name_index;
   
   
// mstudioflexdesc_t
    // mstudioflexdesc_t
int flexdesc_count;
    int       flexdesc_count;
int flexdesc_index;
    int       flexdesc_index;
   
   
// mstudioflexcontroller_t
    // mstudioflexcontroller_t
int flexcontroller_count;
    int       flexcontroller_count;
int flexcontroller_index;
    int       flexcontroller_index;
   
   
// mstudioflexrule_t
    // mstudioflexrule_t
int flexrules_count;
    int       flexrules_count;
int flexrules_index;
    int       flexrules_index;
   
   
// IK probably referse to inverse kinematics
    // IK probably referse to inverse kinematics
// mstudioikchain_t
    // mstudioikchain_t
int ikchain_count;
    int       ikchain_count;
int ikchain_index;
    int       ikchain_index;
   
   
// Information about any "mouth" on the model for speech animation
    // Information about any "mouth" on the model for speech animation
// More than one sounds pretty creepy.
    // More than one sounds pretty creepy.
// mstudiomouth_t
    // mstudiomouth_t
int mouths_count;  
    int       mouths_count;  
int mouths_index;
    int       mouths_index;
   
   
// mstudioposeparamdesc_t
    // mstudioposeparamdesc_t
int localposeparam_count;
    int       localposeparam_count;
int localposeparam_index;
    int       localposeparam_index;
   
   
/*
    /*
* For anyone trying to follow along, as of this writing,
    * For anyone trying to follow along, as of this writing,
* the next "surfaceprop_index" value is at position 0x0134 (308)
    * the next "surfaceprop_index" value is at position 0x0134 (308)
* from the start of the file.
    * from the start of the file.
*/
    */
   
   
// Surface property value (single null-terminated string)
    // Surface property value (single null-terminated string)
int surfaceprop_index;
    int       surfaceprop_index;
   
   
// Unusual: In this one index comes first, then count.
    // Unusual: In this one index comes first, then count.
// Key-value data is a series of strings. If you can't find
    // Key-value data is a series of strings. If you can't find
// what you're interested in, check the associated PHY file as well.
    // what you're interested in, check the associated PHY file as well.
int keyvalue_index;
    int       keyvalue_index;
int keyvalue_count;
    int       keyvalue_count;  
   
   
// More inverse-kinematics
    // More inverse-kinematics
// mstudioiklock_t
    // mstudioiklock_t
int iklock_count;
    int       iklock_count;
int iklock_index;
    int       iklock_index;
   
   
   
   
float mass; // Mass of object (4-bytes)
    float     mass;     // Mass of object (4-bytes) in kilograms
int contents; // ??
 
    int       contents;    // contents flag, as defined in bspflags.h
                            // not all content types are valid; see
                            // documentation on $contents QC command
   
   
// Other models can be referenced for re-used sequences and animations
    // Other models can be referenced for re-used sequences and animations
// (See also: The $includemodel QC option.)
    // (See also: The $includemodel QC option.)
// mstudiomodelgroup_t
    // mstudiomodelgroup_t
int includemodel_count;
    int       includemodel_count;
int includemodel_index
    int       includemodel_index;
   
int virtualModel; // Placeholder for mutable-void*
    int       virtualModel;   // Placeholder for mutable-void*
    // Note that the SDK only compiles as 32-bit, so an int and a pointer are the same size (4 bytes)
   
   
// mstudioanimblock_t
    // mstudioanimblock_t
int animblocks_name_index;
    int       animblocks_name_index;
int animblocks_count;
    int       animblocks_count;
int animblocks_index;
    int       animblocks_index;
   
int animblockModel; // Placeholder for mutable-void*
    int       animblockModel; // Placeholder for mutable-void*
 
    // Points to a series of bytes?
    int        bonetablename_index;
   
    int        vertex_base;    // Placeholder for void*
    int        offset_base;    // Placeholder for void*
   
    // Used with $constantdirectionallight from the QC
    // Model should have flag #13 set if enabled
    byte        directionaldotproduct;
   
    byte        rootLod;    // Preferred rather than clamped
   
    // 0 means any allowed, N means Lod 0 -> (N-1)
    byte        numAllowedRootLods;   
   
    byte        unused0; // ??
    int        unused1; // ??
   
    // mstudioflexcontrollerui_t
    int        flexcontrollerui_count;
    int        flexcontrollerui_index;


// Points to a series of bytes?
    float      vertAnimFixedPointScale; // ??
int bonetablename_index;
    int         unused2;
   
int vertex_base; // Placeholder for void*
    /**
int offset_base; // Placeholder for void*
    * Offset for additional header information.
    * May be zero if not present, or also 408 if it immediately  
// Used with $constantdirectionallight from the QC
    * follows this studiohdr_t
// Model should have flag #13 set if enabled
    */
byte directionaldotproduct;
    // studiohdr2_t
    int         studiohdr2index;
byte rootLod; // Preferred rather than clamped
   
    int         unused3; // ??
// 0 means any allowed, N means Lod 0 -> (N-1)
   
byte numAllowedRootLods;
    /**
    * As of this writing, the header is 408 bytes long in total
byte unused; // ??
    */
int unused; // ??
};
// mstudioflexcontrollerui_t
int flexcontrollerui_count;
int flexcontrollerui_index;
/**
* Offset for additional header information.
* May be zero if not present, or also 408 if it immediately  
* follows this studiohdr_t
*/
// studiohdr2_t
int studiohdr2index;
int unused; // ??
/**
* As of this writing, the header is 408 bytes long in total
*/
}
</source>
</source>


{| border="1"
=== Model flags ===
|+Known flags
{| class="wikitable"
! Bit
! Name
! Name
! Position
! Dev comment
! Details
! Description
|-
| 0x00000001
| STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX
| This flag is set if no hitbox information was specified
| Set by StudioMDL if no [[$hbox]] QC commands were used
|-
| 0x00000002
| STUDIOHDR_FLAGS_USES_ENV_CUBEMAP
| NOTE:  This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials.
| Set by game engine if VMTs used for the model have {{cmd|$envmap|env_cubemap}}.
|-
| 0x00000004
| STUDIOHDR_FLAGS_FORCE_OPAQUE
| Use this when there are translucent parts to the model but we're not going to sort it
| Set by StudioMDL if the [[$opaque]] QC command is used
|-
|-
|STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX
| 0x00000008
|0
| STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS
|
| Use this when we want to render the opaque parts during the opaque pass and the translucent parts during the translucent pass
| Set by StudioMDL if the [[$mostlyopaque]] QC command is used
|-
|-
|STUDIOHDR_FLAGS_USES_ENV_CUBEMAP
| 0x00000010
|1
| STUDIOHDR_FLAGS_STATIC_PROP
|
| This is set any time the .qc files has $staticprop in it
 
Means there's no bones and no transforms
| Set by StudioMDL if the [[$staticprop]] QC command is used. Required for [[prop_static]] and [[prop_physics]].
|-
|-
|STUDIOHDR_FLAGS_FORCE_OPAQUE
| 0x00000020
|2
| STUDIOHDR_FLAGS_USES_FB_TEXTURE
|
| NOTE:  This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials.
| Set by game engine; has something to do with power-of-two framebuffer textures.
|-
|-
|STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS
| 0x00000040
|3
| STUDIOHDR_FLAGS_HASSHADOWLOD
|
| This flag is set by studiomdl.exe if a separate "$shadowlod" entry was present for the .mdl (the shadow lod is the last entry in the lod list if present)
| Set by StudioMDL if the [[$shadowlod]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_STATIC_PROP
| 0x00000080
|4
| STUDIOHDR_FLAGS_USES_BUMPMAPPING
|
| NOTE:  This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials.
| Set by game engine if [[$bumpmap]] or [[$normalmap]] are present in any of the models's VMTs.
|-
|-
|STUDIOHDR_FLAGS_USES_FB_TEXTURE
| 0x00000100
|5
| STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS
|
| NOTE:  This flag is set when we should use the actual materials on the shadow LOD instead of overriding them with the default one (necessary for translucent shadows)
| Set by StudioMDL if the [[$shadowlod]] command has the {{mono|use_shadowlod_materials}} parameter
|-
|-
|STUDIOHDR_FLAGS_HASSHADOWLOD
| 0x00000200
|6
| STUDIOHDR_FLAGS_OBSOLETE
|
| NOTE:  This flag is set when we should use the actual materials on the shadow LOD instead of overriding them with the default one (necessary for translucent shadows)
| Set by StudioMDL if the [[$obsolete]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_USES_BUMPMAPPING
| 0x00000400
|7
| STUDIOHDR_FLAGS_UNUSED
|
| N/A
|  
|-
|-
|STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS
| 0x00000800
|8
| STUDIOHDR_FLAGS_NO_FORCED_FADE
|
| NOTE:  This flag is set at mdl build time
| Set by StudioMDL if the [[$noforcedfade]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_OBSOLETE
| 0x00001000
|9
| STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE
|
| NOTE:  The npc will lengthen the viseme check to always include two phonemes
| Set by StudioMDL if the [[$forcephonemecrossfade]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_UNUSED
| 0x00002000
|10
| STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT
|
| This flag is set when the .qc has $constantdirectionallight in it
 
If set, we use constantdirectionallightdot to calculate light intensity rather than the normal directional dot product
 
only valid if STUDIOHDR_FLAGS_STATIC_PROP is also set
| Set by StudioMDL if the [[$constantdirectionallight]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_NO_FORCED_FADE
| 0x00004000
|11
| STUDIOHDR_FLAGS_FLEXES_CONVERTED
|
| Flag to mark delta flexes as already converted from disk format to memory format
| Used by engine at runtime
|-
|-
|STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE
| 0x00008000
|12
| STUDIOHDR_FLAGS_BUILT_IN_PREVIEW_MODE
|
| Indicates the studiomdl was built in preview mode
| Model has not been optimized into tristrips, and DMX quads have not been triangulated.
 
Set by running [[StudioMDL]] with {{code|-preview}} parameter.
|-
|-
|STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT
| 0x00010000
|13
| STUDIOHDR_FLAGS_AMBIENT_BOOST
|
| Ambient boost (runtime flag)
| Contrary to the dev comment, this can be set by StudioMDL using the [[$ambientboost]] QC command.
|-
|-
|STUDIOHDR_FLAGS_FLEXES_CONVERTED
| 0x00020000
|14
| STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS
|
| Don't cast shadows from this model (useful on first-person models)
| Set by StudioMDL if the [[$donotcastshadows]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_BUILT_IN_PREVIEW_MODE
| 0x00040000
|15
| STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS
|
| alpha textures should cast shadows in vrad on this model (ONLY prop_static!)
| Set by StudioMDL if the [[$casttextureshadows]] QC command is used.
|-
|-
|STUDIOHDR_FLAGS_AMBIENT_BOOST
| 0x00080000
|16
| N/A
| N/A (undefined)
|
|
|-
|-
|STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS
| 0x00100000
|17
| N/A
| N/A (undefined)
|
|
|-
|-
|STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS
| 0x00200000
|18
| STUDIOHDR_FLAGS_VERT_ANIM_FIXED_POINT_SCALE
|
| flagged on load to indicate no animation events on this model
|  
|}
|}


Line 282: Line 334:
<source lang="cpp">struct studiohdr2_t
<source lang="cpp">struct studiohdr2_t
{
{
// ??
        // ??
int srcbonetransform_count;
        int   srcbonetransform_count;
int srcbonetransform_index;
        int   srcbonetransform_index;


int illumpositionattachmentindex;
        int   illumpositionattachmentindex;


float flMaxEyeDeflection; //  If set to 0, then equivalent to cos(30)
        float flMaxEyeDeflection;   //  If set to 0, then equivalent to cos(30)
 
// mstudiolinearbone_t
        // mstudiolinearbone_t
int linearbone_index;
        int   linearbone_index;


int[64] unknown;
        int   unknown[64];
}
};
</source>
</source>


Line 300: Line 352:
<source lang="cpp">struct mstudiotexture_t
<source lang="cpp">struct mstudiotexture_t
{
{
// Number of bytes past the beginning of this structure
        // Number of bytes past the beginning of this structure
// where the first character of the texture name can be found.
        // where the first character of the texture name can be found.
int name_offset; // Offset for null-terminated string
        int   name_offset; // Offset for null-terminated string
int flags;
        int   flags;
int used; // ??
 
        int   used;       // Padding?
int unused; // ??
        int   unused;     // Padding.


int material; // Placeholder for IMaterial
        int   material;       // Placeholder for IMaterial
int client_material; // Placeholder for void*
        int   client_material; // Placeholder for void*
int[10] unused2;
        int   unused2[10]; // Final padding
}
        // Struct is 64 bytes long
};
</source>
</source>


=== Skin family data ===
=== Skin replacement tables ===


Each "skin" that a model has (as seen in the [[Model Viewer]] and choose-able for [[Prop_Types_Overview|prop entities]]) is actually referred to as a skin "family" in the MDL code. For the purposes of this section, a "skin" an area where a single material (aka texture) may be applied, while a skin-family defines a list of materials to use on skin zones 0,1,2, etc.  
Each "skin" that a model has (as seen in the [[Model Viewer]] and choose-able for [[Prop_Types_Overview|prop entities]]) is actually referred to as a skin "family" in the MDL code. (Additional skins may be created with the [[$texturegroup]] compile option.) For the purposes of this section, a "skin" an area where a single material (aka texture) may be applied, while a skin-family defines a list of materials to use on skin zones 0,1,2, etc.  


The skin-family section of the MDL is a sequence of <code>short</code> (2-byte) values, which should be broken up into a table. To illustrate how it works, we will consider a model and then examine how it would be represented on the byte-level. For this example, imagine a crate-model with three skin-families labeled (fam0,fam1,fam2) two skins-zones (s0,s1) and three textures (lightwood,darkwood,metal).  
The skin-family section of the MDL is a sequence of <code>short</code> (2-byte) values, which should be broken up into a table. To illustrate how it works, we will consider a model and then examine how it would be represented on the byte-level. For this example, imagine a crate-model with three skin-families labeled (fam0,fam1,fam2) two skins-zones (mainbody,trimming) and three textures (lightwood,darkwood,metal).  


{| border="1"
{| class="standard-table"
|+Skin table
|+Skin table
!  
!  
! s0
! mainbody
! s1
! trimming
|-
|-
!fam0
!fam0
Line 342: Line 395:
Let's assume that the various textures are given ID values in the MDL like so:
Let's assume that the various textures are given ID values in the MDL like so:


{|border="1"
{| class="standard-table"
|+Texture IDs
|+Texture IDs
!ID
!ID
Line 367: Line 420:
The total number of bytes is <code>numskinfamilies*numskins*2</code>.
The total number of bytes is <code>numskinfamilies*numskins*2</code>.


{{note|MDLs may often have a texture-replacement table which is larger than necessary, with additional columns which are never used.}}
{{todo|Discover how to accurately detect which columns are meaningful in too-large replacement tables.}}


== See also ==
== See also ==
* [[3D Model]]
* [[Model Viewer]]
* [[Model Viewer]]
* [[Model Creation Overview]]
* [[Model Creation Overview]]
* [[Model]]
{{Source topicon}} <!-- topicon automatically adds Category:Source.  -->
 
[[Category:Glossary]]
[[Category:Modeling]]
[[Category:Modeling]]
[[Category:File formats]]

Latest revision as of 11:29, 3 February 2025

English (en)Translate (Translate)
edit

MDL is the extension for Source's proprietary model format. It defines the structure of the model along with animation, bounding box, hit box, materials, mesh and LOD information. It does not, however, contain all the information needed for the model. Additional data is stored in PHY, ANI, VTX and VVD files, and sometimes, usually for shared animations, other .mdl files.

File format

Some details of the file format may be gleaned from the source code in Valve's studio.h, specifically the struct studiohdr_t. The early header defines a series of offsets and lengths for various sub-sections within the file, along with some key scalar information. The MDL also contains the names of materials (VMT), which may be used and referenced in various ways.

Main header

To get the latest header for specific game, please use the studio.h file in the Valve's SDK instead.

struct studiohdr_t
{
    int         id;             // Model format ID, such as "IDST" (0x49 0x44 0x53 0x54)
    int         version;        // Format version number, such as 48 (0x30,0x00,0x00,0x00)
    int         checksum;       // This has to be the same in the phy and vtx files to load!
    char        name[64];       // The internal name of the model, padding with null bytes.
                                // Typically "my_model.mdl" will have an internal name of "my_model"
    int         dataLength;     // Data size of MDL file in bytes.
 
    // A vector is 12 bytes, three 4-byte float-values in a row.
    Vector      eyeposition;    // Position of player viewpoint relative to model origin
    Vector      illumposition;  // Position (relative to model origin) used to calculate ambient light contribution and cubemap reflections for the entire model.
    Vector      hull_min;       // Corner of model hull box with the least X/Y/Z values
    Vector      hull_max;       // Opposite corner of model hull box
    Vector      view_bbmin;     // Same, but for bounding box,
    Vector      view_bbmax;     // which is used for view culling
 
    int         flags;          // Binary flags in little-endian order. 
                                // ex (0x010000C0) means flags for position 0, 30, and 31 are set. 
                                // Set model flags section for more information
 
    /*
     * After this point, the header contains many references to offsets
     * within the MDL file and the number of items at those offsets.
     *
     * Offsets are from the very beginning of the file.
     * 
     * Note that indexes/counts are not always paired and ordered consistently.
     */    
 
    // mstudiobone_t
    int        bone_count;    // Number of data sections (of type mstudiobone_t)
    int        bone_offset;   // Offset of first data section
 
    // mstudiobonecontroller_t
    int        bonecontroller_count;
    int        bonecontroller_offset;
 
    // mstudiohitboxset_t
    int        hitbox_count;
    int        hitbox_offset;
 
    // mstudioanimdesc_t
    int        localanim_count;
    int        localanim_offset;
 
    // mstudioseqdesc_t
    int        localseq_count;
    int        localseq_offset;
 
    int        activitylistversion; // ??
    int        eventsindexed;       // ??
 
    // VMT texture filenames
    // mstudiotexture_t
    int        texture_count;
    int        texture_offset;
 
    // This offset points to a series of ints.
    // Each int value, in turn, is an offset relative to the start of this header/the-file,
    // At which there is a null-terminated string.
    int        texturedir_count;
    int        texturedir_offset;
 
    // Each skin-family assigns a texture-id to a skin location
    int        skinreference_count;
    int        skinrfamily_count;
    int        skinreference_index;
 
    // mstudiobodyparts_t
    int        bodypart_count;
    int        bodypart_offset;
 
    // Local attachment points        
    // mstudioattachment_t
    int        attachment_count;
    int        attachment_offset;
 
    // Node values appear to be single bytes, while their names are null-terminated strings.
    int        localnode_count;
    int        localnode_index;
    int        localnode_name_index;
 
    // mstudioflexdesc_t
    int        flexdesc_count;
    int        flexdesc_index;
 
    // mstudioflexcontroller_t
    int        flexcontroller_count;
    int        flexcontroller_index;
 
    // mstudioflexrule_t
    int        flexrules_count;
    int        flexrules_index;
 
    // IK probably referse to inverse kinematics
    // mstudioikchain_t
    int        ikchain_count;
    int        ikchain_index;
 
    // Information about any "mouth" on the model for speech animation
    // More than one sounds pretty creepy.
    // mstudiomouth_t
    int        mouths_count; 
    int        mouths_index;
 
    // mstudioposeparamdesc_t
    int        localposeparam_count;
    int        localposeparam_index;
 
    /*
     * For anyone trying to follow along, as of this writing,
     * the next "surfaceprop_index" value is at position 0x0134 (308)
     * from the start of the file.
     */
 
    // Surface property value (single null-terminated string)
    int        surfaceprop_index;
 
    // Unusual: In this one index comes first, then count.
    // Key-value data is a series of strings. If you can't find
    // what you're interested in, check the associated PHY file as well.
    int        keyvalue_index;
    int        keyvalue_count;    
 
    // More inverse-kinematics
    // mstudioiklock_t
    int        iklock_count;
    int        iklock_index;
 
 
    float      mass;      // Mass of object (4-bytes) in kilograms

    int        contents;    // contents flag, as defined in bspflags.h
                            // not all content types are valid; see 
                            // documentation on $contents QC command
 
    // Other models can be referenced for re-used sequences and animations
    // (See also: The $includemodel QC option.)
    // mstudiomodelgroup_t
    int        includemodel_count;
    int        includemodel_index;
    
    int        virtualModel;    // Placeholder for mutable-void*
    // Note that the SDK only compiles as 32-bit, so an int and a pointer are the same size (4 bytes)
 
    // mstudioanimblock_t
    int        animblocks_name_index;
    int        animblocks_count;
    int        animblocks_index;
    
    int        animblockModel; // Placeholder for mutable-void*

    // Points to a series of bytes?
    int        bonetablename_index;
    
    int        vertex_base;    // Placeholder for void*
    int        offset_base;    // Placeholder for void*
    
    // Used with $constantdirectionallight from the QC 
    // Model should have flag #13 set if enabled
    byte        directionaldotproduct;
    
    byte        rootLod;    // Preferred rather than clamped
    
    // 0 means any allowed, N means Lod 0 -> (N-1)
    byte        numAllowedRootLods;    
    
    byte        unused0; // ??
    int         unused1; // ??
    
    // mstudioflexcontrollerui_t
    int         flexcontrollerui_count;
    int         flexcontrollerui_index;

    float       vertAnimFixedPointScale; // ??
    int         unused2;
    
    /**
     * Offset for additional header information.
     * May be zero if not present, or also 408 if it immediately 
     * follows this studiohdr_t
     */
    // studiohdr2_t
    int         studiohdr2index;
    
    int         unused3; // ??
    
    /**
     * As of this writing, the header is 408 bytes long in total
     */
};

Model flags

Bit Name Dev comment Description
0x00000001 STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX This flag is set if no hitbox information was specified Set by StudioMDL if no $hbox QC commands were used
0x00000002 STUDIOHDR_FLAGS_USES_ENV_CUBEMAP NOTE: This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials. Set by game engine if VMTs used for the model have $envmap env_cubemap.
0x00000004 STUDIOHDR_FLAGS_FORCE_OPAQUE Use this when there are translucent parts to the model but we're not going to sort it Set by StudioMDL if the $opaque QC command is used
0x00000008 STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS Use this when we want to render the opaque parts during the opaque pass and the translucent parts during the translucent pass Set by StudioMDL if the $mostlyopaque QC command is used
0x00000010 STUDIOHDR_FLAGS_STATIC_PROP This is set any time the .qc files has $staticprop in it

Means there's no bones and no transforms

Set by StudioMDL if the $staticprop QC command is used. Required for prop_static and prop_physics.
0x00000020 STUDIOHDR_FLAGS_USES_FB_TEXTURE NOTE: This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials. Set by game engine; has something to do with power-of-two framebuffer textures.
0x00000040 STUDIOHDR_FLAGS_HASSHADOWLOD This flag is set by studiomdl.exe if a separate "$shadowlod" entry was present for the .mdl (the shadow lod is the last entry in the lod list if present) Set by StudioMDL if the $shadowlod QC command is used.
0x00000080 STUDIOHDR_FLAGS_USES_BUMPMAPPING NOTE: This flag is set at loadtime, not mdl build time so that we don't have to rebuild models when we change materials. Set by game engine if $bumpmap or $normalmap are present in any of the models's VMTs.
0x00000100 STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS NOTE: This flag is set when we should use the actual materials on the shadow LOD instead of overriding them with the default one (necessary for translucent shadows) Set by StudioMDL if the $shadowlod command has the use_shadowlod_materials parameter
0x00000200 STUDIOHDR_FLAGS_OBSOLETE NOTE: This flag is set when we should use the actual materials on the shadow LOD instead of overriding them with the default one (necessary for translucent shadows) Set by StudioMDL if the $obsolete QC command is used.
0x00000400 STUDIOHDR_FLAGS_UNUSED N/A
0x00000800 STUDIOHDR_FLAGS_NO_FORCED_FADE NOTE: This flag is set at mdl build time Set by StudioMDL if the $noforcedfade QC command is used.
0x00001000 STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE NOTE: The npc will lengthen the viseme check to always include two phonemes Set by StudioMDL if the $forcephonemecrossfade QC command is used.
0x00002000 STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT This flag is set when the .qc has $constantdirectionallight in it

If set, we use constantdirectionallightdot to calculate light intensity rather than the normal directional dot product

only valid if STUDIOHDR_FLAGS_STATIC_PROP is also set

Set by StudioMDL if the $constantdirectionallight QC command is used.
0x00004000 STUDIOHDR_FLAGS_FLEXES_CONVERTED Flag to mark delta flexes as already converted from disk format to memory format Used by engine at runtime
0x00008000 STUDIOHDR_FLAGS_BUILT_IN_PREVIEW_MODE Indicates the studiomdl was built in preview mode Model has not been optimized into tristrips, and DMX quads have not been triangulated.

Set by running StudioMDL with -preview parameter.

0x00010000 STUDIOHDR_FLAGS_AMBIENT_BOOST Ambient boost (runtime flag) Contrary to the dev comment, this can be set by StudioMDL using the $ambientboost QC command.
0x00020000 STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS Don't cast shadows from this model (useful on first-person models) Set by StudioMDL if the $donotcastshadows QC command is used.
0x00040000 STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS alpha textures should cast shadows in vrad on this model (ONLY prop_static!) Set by StudioMDL if the $casttextureshadows QC command is used.
0x00080000 N/A N/A (undefined)
0x00100000 N/A N/A (undefined)
0x00200000 STUDIOHDR_FLAGS_VERT_ANIM_FIXED_POINT_SCALE flagged on load to indicate no animation events on this model

Secondary header

This header section is optional, and is found via the studiohdr2index value.

struct studiohdr2_t
{
        // ??
        int    srcbonetransform_count;
        int    srcbonetransform_index;

        int    illumpositionattachmentindex;

        float  flMaxEyeDeflection;    //  If set to 0, then equivalent to cos(30)

        // mstudiolinearbone_t
        int    linearbone_index;

        int    unknown[64];
};

Texture data

struct mstudiotexture_t
{
        // Number of bytes past the beginning of this structure
        // where the first character of the texture name can be found.
        int    name_offset; // Offset for null-terminated string
        int    flags;

        int    used;        // Padding?
        int    unused;      // Padding.

        int    material;        // Placeholder for IMaterial
        int    client_material; // Placeholder for void*
	
        int    unused2[10]; // Final padding
        // Struct is 64 bytes long
};

Skin replacement tables

Each "skin" that a model has (as seen in the Model Viewer and choose-able for prop entities) is actually referred to as a skin "family" in the MDL code. (Additional skins may be created with the $texturegroup compile option.) For the purposes of this section, a "skin" an area where a single material (aka texture) may be applied, while a skin-family defines a list of materials to use on skin zones 0,1,2, etc.

The skin-family section of the MDL is a sequence of short (2-byte) values, which should be broken up into a table. To illustrate how it works, we will consider a model and then examine how it would be represented on the byte-level. For this example, imagine a crate-model with three skin-families labeled (fam0,fam1,fam2) two skins-zones (mainbody,trimming) and three textures (lightwood,darkwood,metal).

Skin table
mainbody trimming
fam0 lightwood metal
fam1 darkwood metal
fam2 metal metal

Let's assume that the various textures are given ID values in the MDL like so:

Texture IDs
ID Name
0 lightwood
1 darkwood
2 metal
Note.pngNote:This ordering frequently matches the "VMTs Loaded" display of HLMV

In the MDL data, this table relationship is broken down into a stream of bytes, with two-byte texture IDs replacing the literal names. Thus the final series of bytes that corresponds to these skin-family relationships would (as little-endian short values) be:

00 00   02 00
01 00   02 00
02 00   02 00

The total number of bytes is numskinfamilies*numskins*2.

Note.pngNote:MDLs may often have a texture-replacement table which is larger than necessary, with additional columns which are never used.
Todo: Discover how to accurately detect which columns are meaningful in too-large replacement tables.

See also