DMX model

From Valve Developer Community
Jump to navigation Jump to search

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"