DMX model: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
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]]. Below is the structure of '''version 18''', which can be compiled by [[Alien Swarm (engine branch)|Alien Swarm]] studiomdl. [[Source 2009]] studiomdl requires version 1.
{{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.}}


<source lang=cpp>
==Features==
class DmeModelRoot
These features require DMX:
{
// model and skeleton should point to the same object
DmeModel* model;
DmeModel* skeleton;
DmeCombinationOperator* combinationOperator; // flex animations only
};


class DmeCombinationOperator // flex controller global settings
* [[Wrinklemaps]]
{
* [[Flex animation#Corrective shapes|Corrective shapes]]
DmeCombinationInputControl controls[];
* [[DMX/Source 2 Vertex attributes| Source 2 Vertex attributes]]
Vector controlValues[]; // rest position...but why a 3D vector?
Vector controlValuesLagged[]; // unknown
bool usesLaggedValues;
DmeMesh* dominators[]; // unknown
DmeMesh* targets[]; // mesh with the shapes on
};
 
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[]; // wrinklemap stuff; one value for each named control.
};


class DmeModel // one per file
==Versions==
{
; 1
DmeTransform* transform; // global model transform...DmeDag not supported here?
: Source 2007
void shape; // only seen empty (Compatibility with DmeDag?)
: Source 2009
bool visible;
: Half-Life 2
DmeDag / DmeJoint children[]; // mixed type
: Source SDK Base Singleplayer 2013
DmeJoint jointList[];
: Source SDK Base Multiplayer 2013
DmeTransformList baseStates[]; // bone positions; only ever seen with one value
; 15
CUtlString upAxis; // uppercase character
: 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 generic Maya container, appearing here in order to transform objects.
==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* transform;
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;
void children[]; // never seen used (Compatibility with DmeModel?)
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* shape;
DmeMesh shape;
DmeAttachment* shape;
DmeAttachment shape;
};
};


class DmeJoint // a bone
class DmeJoint : DmeDag // a bone
{
{
DmeTransform* transform;
DmeTransform transform; // used by SFM to store the *current* position of the bone.
void* shape; // only seen empty
// 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 // name is of a defined model if there are children, otherwise blank
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 IsRigid; // does not animate with associated bone
bool isRigid; // apparently obsolete?
bool isWorldAligned; // transform in world co-ords
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* bindState; // only seen empty
void bindState; // only seen empty
DmeVertexData* currentState; // pointer to default baseState
DmeVertexData currentState; // pointer to default baseState
DmeVertexData baseStates[]; // only ever seen with one value
DmeVertexData baseStates[]; // only ever seen with one value
DmeVertexData deltaStates[]; // unknown
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; // total number of bones this mesh is weighted to
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?


// In the next three cases, the "Indicies" array contains one entry for each vertex.
// The first array contains one entry per vertex.
// the indice value defines which value from the 'real' array the vertex takes. In this
// The second "Indices" array contains one entry one entry per vertex /per face/.
// way, vertices can share data. This is a useful optimisation since each poly has its
// own three verts, even if they overlap other verts.
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 both arrays is equal to ( sizeof(positions) * jointCount );
// Weightmapping; optional. The size of BOTH arrays is equal to ( sizeof(positions) * jointCount )
// it is not possible for two verts in the same place to be weighted differently.
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, including their material
class DmeFaceSet // defines a set of faces with a given material
{
{
DmeMaterial* material; // the material these faces are drawn with
DmeMaterial material; // the material these faces are drawn with
int faces[]; // the vertices that make up each face, delimited by -1. Quads and n-gons allowed.
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; // with path relative to \game\materials\, no extension
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.

Note.pngNote:All names are case sensitive.

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, not wrinklesIndices!

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"