DMX model: Difference between revisions
		
		
		
		
		
		Jump to navigation
		Jump to search
		
				
		
Note:All names are case sensitive.
		
	
TomEdwards (talk | contribs)  (deciphered Dag and Indicies)  | 
				 (The CS:GO being referred to here is the legacy one, not the Source 2 one)  | 
				||
| (39 intermediate revisions by 5 users not shown) | |||
| Line 1: | Line 1: | ||
{{  | {{toc-right}}  | ||
The '''[[DMX]] model format''' replaces [[Studiomdl Data]]. This article describes '''version 18'''.  | |||
{{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)  | |||
==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.  | |||
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;  | |||
};  | |||
</source>  | |||
===Mesh===  | |||
<source lang=cpp>  | <source lang=cpp>  | ||
class DmeModelRoot  | class DmeModelRoot  | ||
{  | {  | ||
	// model and skeleton should point to the same object  | 	// model and skeleton should point to the same object  | ||
	DmeModel  | 	DmeModel	model;  | ||
	DmeModel  | 	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  | |||
};  | };  | ||
</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  | class DmeCombinationOperator // flex controller global settings  | ||
{  | {  | ||
	DmeCombinationInputControl	controls[];  | 	DmeCombinationInputControl		controls[];  | ||
	Vector	  | 	Vector							controlValues[];		// rest position for L/R values  | ||
	Vector	  | 	Vector							controlValuesLagged[];	// lerp factor for changes to the values (if usesLaggedValues == true)  | ||
	bool	  | 	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  | 	DmeMesh							targets[]; // mesh with shapes on, or DmeFlexRules in some old DMX files  | ||
};  | };  | ||
| Line 28: | Line 183: | ||
{  | {  | ||
	CUtlString	rawControlNames[];	// which controls are being wrapped  | 	CUtlString	rawControlNames[];	// which controls are being wrapped  | ||
	bool		stereo;				// equivalent to QC 'split'  | 	bool		stereo;				// equivalent to QC 'split'  | ||
	bool		eyelid;				// flags as an eyelid used by AI for blinking  | 	bool		eyelid;				// flags as an eyelid used by AI for blinking  | ||
	float		wrinkleScales[];	// wrinkle   | 	float		wrinkleScales[];	// records the scale used to generate wrinkle data; not read by studiomdl  | ||
	float		flexMin;  | |||
	float		flexMax;  | |||
};  | };  | ||
class   | 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  | |||
	//   | |||
};  | };  | ||
class   | // 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>  | |||
class   | ===Animation===  | ||
<source lang=cpp>  | |||
class DmeModelRoot  | |||
{  | {  | ||
	DmeModel	skeleton;  | |||
	DmeAnimationList	animationList;  | |||
};  | };  | ||
class   | class DmeAnimationList  | ||
{  | {  | ||
	DmeChannelsClip animations[];  | |||
};  | };  | ||
class   | class DmeChannelsClip  | ||
{  | {  | ||
	bool	  | 	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   | 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;  | |||
	// the   | |||
	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   | class DmeQuaternionLog // also DmeVector3Log etc.  | ||
{  | {  | ||
	DmeQuaternionLogLayer layers[];  | |||
	CDmxElement	curveinfo;  | |||
	bool			usedefaultvalue;  | |||
	Quaternion		defaultvalue;  | |||
};  | };  | ||
class   | 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 04: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::normalsshould 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 
DmeTimeattribute type. - Renamed "durationTime" to "duration"
 - Renamed "offsetTime" to "offset"
 
15
DmeModel- Added "jointList" alongside "jointTransforms"