DMX model: Difference between revisions
Jump to navigation
Jump to search
Note:All names are case sensitive.
TomEdwards (talk | contribs) mNo edit summary |
(The CS:GO being referred to here is the legacy one, not the Source 2 one) |
||
(29 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
The '''[[DMX]] model format''' replaces [[Studiomdl Data]]. | {{toc-right}} | ||
The '''[[DMX]] model format''' replaces [[Studiomdl Data]]. This article describes '''version 18'''. | |||
{{note|All names are case sensitive.}} | {{note|All names are case sensitive.}} | ||
==Features== | |||
These features require DMX: | |||
* [[Wrinklemaps]] | |||
* [[Flex animation#Corrective shapes|Corrective shapes]] | |||
* [[DMX/Source 2 Vertex attributes| Source 2 Vertex attributes]] | |||
==Versions== | |||
; 1 | |||
: Source 2007 | |||
: Source 2009 | |||
: Half-Life 2 | |||
: Source SDK Base Singleplayer 2013 | |||
: Source SDK Base Multiplayer 2013 | |||
; 15 | |||
: Left 4 Dead | |||
: Left 4 Dead 2 | |||
; 18 | |||
: Alien Swarm | |||
: Source MP | |||
: Portal 2 | |||
: Source Filmmaker | |||
: Counter-Strike: Global Offensive (Legacy version) | |||
// A "DAG" is a | ==Layout== | ||
===Common=== | |||
<source lang=cpp> | |||
// A "DAG" is a node in a Directed acyclic graph, constructing the scene hierarchy. | |||
// It appears here in order to transform its child objects. | |||
// It can take the place of a DmeMesh, DmeJoint or DmeAttachment at any time. | // It can take the place of a DmeMesh, DmeJoint or DmeAttachment at any time. | ||
class DmeDag | class DmeDag | ||
{ | { | ||
DmeTransform | DmeTransform transform; // used by SFM to store the *current* position of the dag. | ||
// Studiomdl only reads this value if nothing is found in DmeModel::baseStates::DmeTransformList. | |||
bool visible; | bool visible; | ||
DmeDag children[]; // child nodes of the dag. never seen used (Compatibility with DmeModel?) | |||
// One of the following: | // One of the following: | ||
CUtlString name // of a DmeJoint | CUtlString name; // of a DmeJoint | ||
DmeMesh | DmeMesh shape; | ||
DmeAttachment | DmeAttachment shape; | ||
}; | }; | ||
class DmeJoint // a bone | class DmeJoint : DmeDag // a bone | ||
{ | { | ||
DmeTransform | DmeTransform transform; // used by SFM to store the *current* position of the bone. | ||
void | // Studiomdl only reads this value if nothing is found in DmeModel::baseStates::DmeTransformList. | ||
void shape; // only seen empty | |||
bool visible; | bool visible; | ||
DmeJoint children[]; | DmeJoint children[]; | ||
Line 67: | Line 60: | ||
class DmeTransformList | class DmeTransformList | ||
{ | { | ||
DmeTransform transforms[]; | DmeTransform transforms[]; // contains one element for each bone and mesh. Elements are matched according to name. | ||
}; | }; | ||
class DmeTransform | class DmeTransform | ||
{ | { | ||
Vector position; | Vector position; | ||
Quaternion orientation; | Quaternion orientation; | ||
}; | |||
</source> | |||
===Mesh=== | |||
<source lang=cpp> | |||
class DmeModelRoot | |||
{ | |||
// model and skeleton should point to the same object | |||
DmeModel model; | |||
DmeModel skeleton; | |||
DmeCombinationOperator combinationOperator; // flex controllers; optional | |||
}; | }; | ||
class DmeAttachment // an attachment | class DmeModel // one per file | ||
{ | |||
bool visible; | |||
DmeDag children[]; | |||
DmeJoint jointList[]; | |||
DmeTransformList baseStates[]; // defines bone and mesh positions; only ever seen with one value | |||
}; | |||
class DmeAttachment : DmeDag // an attachment | |||
{ | { | ||
bool visible; | bool visible; | ||
bool | bool isRigid; // apparently obsolete? | ||
bool isWorldAligned; // | bool isWorldAligned; // does not move with parent bone, but is still positioned relative to it | ||
}; | }; | ||
class DmeMesh | class DmeMesh : DmeDag | ||
{ | { | ||
bool visible; | bool visible; | ||
void | void bindState; // only seen empty | ||
DmeVertexData | DmeVertexData currentState; // pointer to default baseState | ||
DmeVertexData baseStates[]; // only ever seen with one value | DmeVertexData baseStates[]; // only ever seen with one value | ||
DmeVertexDeltaData deltaStates[]; // flex shapes | |||
DmeFaceSet faceSets[]; | DmeFaceSet faceSets[]; | ||
Line 100: | Line 112: | ||
class DmeVertexData // mesh data | class DmeVertexData // mesh data | ||
{ | { | ||
CUtlString vertexFormat[]; // positions, normals, textureCoordinates, [jointWeights, jointIndices] | CUtlString vertexFormat[]; // { positions, normals, textureCoordinates, [jointWeights, jointIndices, balance] } | ||
int jointCount; // | int jointCount; // most bones any one vert is weighted to; max 3 (studiomdl errors on compile otherwise) | ||
bool flipVCoordinates; // left-handed to right-handed? | bool flipVCoordinates; // left-handed to right-handed? | ||
// | // The first array contains one entry per vertex. | ||
// | // The second "Indices" array contains one entry one entry per vertex /per face/. | ||
Vector positions[]; | Vector positions[]; | ||
Line 117: | Line 127: | ||
Vector2D textureCoordinates[]; | Vector2D textureCoordinates[]; | ||
int textureCoordinatesIndices[]; | int textureCoordinatesIndices[]; | ||
// Flex controller stereo split; optional | |||
float balance[]; // 0 = 100% right, 1 = 100% left. | |||
int balanceIndices[]; | |||
// Weightmapping; optional. The size of | // Weightmapping; optional. The size of BOTH arrays is equal to ( sizeof(positions) * jointCount ) | ||
float jointWeights[]; // weight | float jointWeights[]; // weight | ||
int jointIndices[]; // index in DmeModel::jointList | int jointIndices[]; // index in DmeModel::jointList (v15+) or DmeModel::jointTransforms (v1-14) | ||
}; | }; | ||
class DmeFaceSet // defines faces | class DmeFaceSet // defines a set of faces with a given material | ||
{ | { | ||
DmeMaterial | DmeMaterial material; // the material these faces are drawn with | ||
int | int faces[]; // the indices of the vertices that make up each face, delimited by -1. Quads and *convex* n-gons allowed. | ||
}; | }; | ||
class DmeMaterial // a material | class DmeMaterial // a material | ||
{ | { | ||
CUtlString mtlName; // | CUtlString mtlName; // path relative to \game\materials\, no extension | ||
}; | |||
class DmeVertexDeltaData // a shape key | |||
{ | |||
CUtlString vertexFormat[]; // positions, normals, [wrinkle] | |||
bool flipVCoordinates; // unknown | |||
bool corrected; // unknown | |||
Vector3 positions[]; // offset: (shape position) - (mesh position) | |||
int positionsIndices[]; // index in DmeMesh::currentState::positions | |||
Vector3 normals[]; // offset: (shape normal) - (mesh normal). For corrective shapes "base" is the mesh plus target shapes. | |||
int normalsIndices[]; // index in DmeMesh::currentState::normals | |||
float wrinkle[]; // wrinkle scale. +1 means full compress, -1 means full stretch. | |||
int wrinkleIndices[]; // index in DmeMesh::currentState::textureCoordinates | |||
}; | |||
</source> | |||
* For corrective shapes, values for <code>DmeVertexDeltaData::normals</code> should be calculated with all target shapes applied to the mesh. | |||
* It's <code>wrinkleIndices</code>, not <code>wrinkle<u>s</u>Indices</code>! | |||
====Flex controllers==== | |||
<source lang=cpp> | |||
class DmeCombinationOperator // flex controller global settings | |||
{ | |||
DmeCombinationInputControl controls[]; | |||
Vector controlValues[]; // rest position for L/R values | |||
Vector controlValuesLagged[]; // lerp factor for changes to the values (if usesLaggedValues == true) | |||
bool usesLaggedValues; // value changes are not instant, but "lagged" (lerp between values - enabled by default in SMD) | |||
DmeCombinationDominationRule dominators[]; // list of domination rules to use | |||
DmeMesh targets[]; // mesh with shapes on, or DmeFlexRules in some old DMX files | |||
}; | |||
class DmeCombinationInputControl // a flex controller | |||
{ | |||
CUtlString rawControlNames[]; // which controls are being wrapped | |||
bool stereo; // equivalent to QC 'split' | |||
bool eyelid; // flags as an eyelid used by AI for blinking | |||
float wrinkleScales[]; // records the scale used to generate wrinkle data; not read by studiomdl | |||
float flexMin; | |||
float flexMax; | |||
}; | |||
class DmeCombinationDominationRule // Disables certain shapes (NOT controllers) when others are active | |||
{ | |||
CUtlString dominators[]; | |||
CUtlString supressed[]; | |||
}; | |||
class DmeFlexRules // shape key pre-processing. Insert at DmeCombinationOperator::targets. | |||
{ | |||
DmeFlexRule deltaStates[]; // mixed type | |||
Vector2 deltaStateWeights[]; | |||
DmeMesh target; // mesh with the shapes on | |||
}; | |||
// In flex rules, the element name must match the name of a DmeVertexDeltaData element on the target DmeMesh. | |||
// It does NOT specify values of controllers. | |||
class DmeFlexRule | |||
{ | |||
float result; | |||
}; | |||
class DmeFlexRuleExpression : DmeFlexRule // Seems to be replaced with "DmeFlexRule" in older versions of DMX? | |||
{ | |||
float result; | |||
CUtlString expression; // +-/() with min, max & sqrt. Flex controller names can be included too, as long as their names don't have spaces! | |||
// L/R split controllers either have the left_ or right_ prefix, or a L or R suffix. | |||
}; | |||
class DmeFlexRulePassThrough : DmeFlexRule // No expression required, shapes are controlled like normal | |||
{ | |||
float result; | |||
}; | |||
</source> | |||
===Animation=== | |||
<source lang=cpp> | |||
class DmeModelRoot | |||
{ | |||
DmeModel skeleton; | |||
DmeAnimationList animationList; | |||
}; | |||
class DmeAnimationList | |||
{ | |||
DmeChannelsClip animations[]; | |||
}; | |||
class DmeChannelsClip | |||
{ | |||
DmeTimeFrame timeFrame; | |||
Colour color; // SFM only | |||
CUtlString text; // SFM only | |||
bool mute; // SFM only | |||
int frameRate; // typically 30 | |||
DmeTrackGroup trackGroups[]; // SFM only | |||
DmeChannel channels[]; // two for each bone: position and rotation | |||
}; | |||
class DmeTimeFrame | |||
{ | |||
DmeTime_t start; // no apparent effect, use offset | |||
DmeTime_t duration; // length in seconds...framerate is NOT adjusted | |||
DmeTime_t offset; // remove this many seconds from the start (can be negative) | |||
float scale; // frameRate multiplier | |||
}; | |||
class DmeChannel | |||
{ | |||
// this format is shared with Source Filmmaker, so has support for animating generic properties. | |||
// Studiomdl only cares about bones though. | |||
CDmxElement fromElement; // TODO: what should this be? | |||
CUtlString fromAttribute; | |||
int fromIndex; | |||
CDmxElement toElement; // ordinarily a DmeTransform used by the target bone | |||
CUtlString toAttribute; | |||
int toIndex; | |||
int mode; // Recording mode for channel - unused by studiomdl. | |||
// One of: | |||
DmeQuaternionLog log[]; | |||
DmeVector3Log log[]; | |||
// etc | |||
}; | |||
class DmeQuaternionLog // also DmeVector3Log etc. | |||
{ | |||
DmeQuaternionLogLayer layers[]; | |||
CDmxElement curveinfo; | |||
bool usedefaultvalue; | |||
Quaternion defaultvalue; | |||
}; | |||
class DmeQuaternionLogLayer // also DmeVector3LogLayer etc. | |||
{ | |||
// only frames where the bone moves need to be given | |||
// unlike SMD, sparse keyframes are supported | |||
DmeTime_t times[]; | |||
int curvetypes[]; // keyframe interp in SFM | |||
Quaternion values[]; | |||
}; | }; | ||
</source> | </source> | ||
==Changes== | |||
This list will inevitably be incomplete. Only versions known about by the public are listed. Format changes generally relate to [[Source Filmmaker]], not Studiomdl. | |||
===18=== | |||
; <code>DmeTimeFrame</code> | |||
: Introduction of <code>DmeTime</code> attribute type. | |||
: Renamed "durationTime" to "duration" | |||
: Renamed "offsetTime" to "offset" | |||
===15=== | |||
; <code>DmeModel</code> | |||
: Added "jointList" alongside "jointTransforms" | |||
[[Category:Modeling]] | |||
[[Category:Technical]] | |||
[[Category:File formats]] |
Latest revision as of 05:07, 4 December 2023
The DMX model format replaces Studiomdl Data. This article describes version 18.

Features
These features require DMX:
Versions
- 1
- Source 2007
- Source 2009
- Half-Life 2
- Source SDK Base Singleplayer 2013
- Source SDK Base Multiplayer 2013
- 15
- Left 4 Dead
- Left 4 Dead 2
- 18
- Alien Swarm
- Source MP
- Portal 2
- Source Filmmaker
- Counter-Strike: Global Offensive (Legacy version)
Layout
Common
// A "DAG" is a node in a Directed acyclic graph, constructing the scene hierarchy.
// It appears here in order to transform its child objects.
// It can take the place of a DmeMesh, DmeJoint or DmeAttachment at any time.
class DmeDag
{
DmeTransform transform; // used by SFM to store the *current* position of the dag.
// Studiomdl only reads this value if nothing is found in DmeModel::baseStates::DmeTransformList.
bool visible;
DmeDag children[]; // child nodes of the dag. never seen used (Compatibility with DmeModel?)
// One of the following:
CUtlString name; // of a DmeJoint
DmeMesh shape;
DmeAttachment shape;
};
class DmeJoint : DmeDag // a bone
{
DmeTransform transform; // used by SFM to store the *current* position of the bone.
// Studiomdl only reads this value if nothing is found in DmeModel::baseStates::DmeTransformList.
void shape; // only seen empty
bool visible;
DmeJoint children[];
bool lockInfluenceWeights;
};
class DmeTransformList
{
DmeTransform transforms[]; // contains one element for each bone and mesh. Elements are matched according to name.
};
class DmeTransform
{
Vector position;
Quaternion orientation;
};
Mesh
class DmeModelRoot
{
// model and skeleton should point to the same object
DmeModel model;
DmeModel skeleton;
DmeCombinationOperator combinationOperator; // flex controllers; optional
};
class DmeModel // one per file
{
bool visible;
DmeDag children[];
DmeJoint jointList[];
DmeTransformList baseStates[]; // defines bone and mesh positions; only ever seen with one value
};
class DmeAttachment : DmeDag // an attachment
{
bool visible;
bool isRigid; // apparently obsolete?
bool isWorldAligned; // does not move with parent bone, but is still positioned relative to it
};
class DmeMesh : DmeDag
{
bool visible;
void bindState; // only seen empty
DmeVertexData currentState; // pointer to default baseState
DmeVertexData baseStates[]; // only ever seen with one value
DmeVertexDeltaData deltaStates[]; // flex shapes
DmeFaceSet faceSets[];
Vector2D deltaStateWeights[]; // unknown
Vector2D deltaStateWeightsLagged[]; // unknown
};
class DmeVertexData // mesh data
{
CUtlString vertexFormat[]; // { positions, normals, textureCoordinates, [jointWeights, jointIndices, balance] }
int jointCount; // most bones any one vert is weighted to; max 3 (studiomdl errors on compile otherwise)
bool flipVCoordinates; // left-handed to right-handed?
// The first array contains one entry per vertex.
// The second "Indices" array contains one entry one entry per vertex /per face/.
Vector positions[];
int positionsIndices[];
Vector normals[];
int normalsIndices[];
Vector2D textureCoordinates[];
int textureCoordinatesIndices[];
// Flex controller stereo split; optional
float balance[]; // 0 = 100% right, 1 = 100% left.
int balanceIndices[];
// Weightmapping; optional. The size of BOTH arrays is equal to ( sizeof(positions) * jointCount )
float jointWeights[]; // weight
int jointIndices[]; // index in DmeModel::jointList (v15+) or DmeModel::jointTransforms (v1-14)
};
class DmeFaceSet // defines a set of faces with a given material
{
DmeMaterial material; // the material these faces are drawn with
int faces[]; // the indices of the vertices that make up each face, delimited by -1. Quads and *convex* n-gons allowed.
};
class DmeMaterial // a material
{
CUtlString mtlName; // path relative to \game\materials\, no extension
};
class DmeVertexDeltaData // a shape key
{
CUtlString vertexFormat[]; // positions, normals, [wrinkle]
bool flipVCoordinates; // unknown
bool corrected; // unknown
Vector3 positions[]; // offset: (shape position) - (mesh position)
int positionsIndices[]; // index in DmeMesh::currentState::positions
Vector3 normals[]; // offset: (shape normal) - (mesh normal). For corrective shapes "base" is the mesh plus target shapes.
int normalsIndices[]; // index in DmeMesh::currentState::normals
float wrinkle[]; // wrinkle scale. +1 means full compress, -1 means full stretch.
int wrinkleIndices[]; // index in DmeMesh::currentState::textureCoordinates
};
- For corrective shapes, values for
DmeVertexDeltaData::normals
should be calculated with all target shapes applied to the mesh. - It's
wrinkleIndices
, notwrinklesIndices
!
Flex controllers
class DmeCombinationOperator // flex controller global settings
{
DmeCombinationInputControl controls[];
Vector controlValues[]; // rest position for L/R values
Vector controlValuesLagged[]; // lerp factor for changes to the values (if usesLaggedValues == true)
bool usesLaggedValues; // value changes are not instant, but "lagged" (lerp between values - enabled by default in SMD)
DmeCombinationDominationRule dominators[]; // list of domination rules to use
DmeMesh targets[]; // mesh with shapes on, or DmeFlexRules in some old DMX files
};
class DmeCombinationInputControl // a flex controller
{
CUtlString rawControlNames[]; // which controls are being wrapped
bool stereo; // equivalent to QC 'split'
bool eyelid; // flags as an eyelid used by AI for blinking
float wrinkleScales[]; // records the scale used to generate wrinkle data; not read by studiomdl
float flexMin;
float flexMax;
};
class DmeCombinationDominationRule // Disables certain shapes (NOT controllers) when others are active
{
CUtlString dominators[];
CUtlString supressed[];
};
class DmeFlexRules // shape key pre-processing. Insert at DmeCombinationOperator::targets.
{
DmeFlexRule deltaStates[]; // mixed type
Vector2 deltaStateWeights[];
DmeMesh target; // mesh with the shapes on
};
// In flex rules, the element name must match the name of a DmeVertexDeltaData element on the target DmeMesh.
// It does NOT specify values of controllers.
class DmeFlexRule
{
float result;
};
class DmeFlexRuleExpression : DmeFlexRule // Seems to be replaced with "DmeFlexRule" in older versions of DMX?
{
float result;
CUtlString expression; // +-/() with min, max & sqrt. Flex controller names can be included too, as long as their names don't have spaces!
// L/R split controllers either have the left_ or right_ prefix, or a L or R suffix.
};
class DmeFlexRulePassThrough : DmeFlexRule // No expression required, shapes are controlled like normal
{
float result;
};
Animation
class DmeModelRoot
{
DmeModel skeleton;
DmeAnimationList animationList;
};
class DmeAnimationList
{
DmeChannelsClip animations[];
};
class DmeChannelsClip
{
DmeTimeFrame timeFrame;
Colour color; // SFM only
CUtlString text; // SFM only
bool mute; // SFM only
int frameRate; // typically 30
DmeTrackGroup trackGroups[]; // SFM only
DmeChannel channels[]; // two for each bone: position and rotation
};
class DmeTimeFrame
{
DmeTime_t start; // no apparent effect, use offset
DmeTime_t duration; // length in seconds...framerate is NOT adjusted
DmeTime_t offset; // remove this many seconds from the start (can be negative)
float scale; // frameRate multiplier
};
class DmeChannel
{
// this format is shared with Source Filmmaker, so has support for animating generic properties.
// Studiomdl only cares about bones though.
CDmxElement fromElement; // TODO: what should this be?
CUtlString fromAttribute;
int fromIndex;
CDmxElement toElement; // ordinarily a DmeTransform used by the target bone
CUtlString toAttribute;
int toIndex;
int mode; // Recording mode for channel - unused by studiomdl.
// One of:
DmeQuaternionLog log[];
DmeVector3Log log[];
// etc
};
class DmeQuaternionLog // also DmeVector3Log etc.
{
DmeQuaternionLogLayer layers[];
CDmxElement curveinfo;
bool usedefaultvalue;
Quaternion defaultvalue;
};
class DmeQuaternionLogLayer // also DmeVector3LogLayer etc.
{
// only frames where the bone moves need to be given
// unlike SMD, sparse keyframes are supported
DmeTime_t times[];
int curvetypes[]; // keyframe interp in SFM
Quaternion values[];
};
Changes
This list will inevitably be incomplete. Only versions known about by the public are listed. Format changes generally relate to Source Filmmaker, not Studiomdl.
18
DmeTimeFrame
- Introduction of
DmeTime
attribute type. - Renamed "durationTime" to "duration"
- Renamed "offsetTime" to "offset"
15
DmeModel
- Added "jointList" alongside "jointTransforms"