DMX model

From Valve Developer Community
Jump to: navigation, search

The DMX model format replaces Studiomdl Data. This article describes version 18.

Note: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
CS:GO

Layout

Common

// A "DAG" is a generic Maya container, appearing here in order to transform 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;
	void			children[]; // never seen used (Compatibility with DmeModel?)
 
	// One of the following:
	CUtlString		name; // of a DmeJoint
	DmeMesh		shape;
	DmeAttachment	shape;
};
 
class DmeJoint // 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|DmeJoint		children[];		// mixed type, contains DmeMesh elements too
	DmeJoint			jointList[];
	DmeTransformList	baseStates[];	// defines bone and mesh positions; only ever seen with one value	
};
 
class DmeAttachment // 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
{
	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
	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...but why a 3D vector?
	Vector							controlValuesLagged[];	// unknown
	bool							usesLaggedValues;
	DmeCombinationDominationRule	dominators[];
	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[];
};

The following element types still exist, but appear to have been obsoleted by domination rules:

class DmeFlexRules // shape key pre-processing. Insert at DmeCombinationOperator::targets.
{
	DmeFlexRule|DmeFlexRulePassThrough	deltaStates[]; // mixed type
	Vector2								deltaStateWeights[];
	DmeMesh								target; // mesh with the shapes on
};
 
// In the next two cases, the element name must match
// the name of a DmeVertexDeltaData element on the target DmeMesh
class DmeFlexRule
{
	float		result;
	CUtlString	expression; // +-/() with min, max & sqrt.
};
class DmeFlexRulePassThrough // no expression required
{
	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
 
	void 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;
	CUtlString		fromAttribute;
	int				fromIndex;
 
	CDmxElement	toElement; // ordinarily a DmeTransform used by the target bone
	CUtlString		toAttribute;
	int				toIndex;
 
	int		mode; // unknown
 
	// 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[]; // only seen empty...keyframe interp?
	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"