> i’m assuming dag objects are Directed Acyclic Graph nodes.
yup, basically the DAG graph holds a series of input/output connections that start you from the original shape and take you through a series of transformations, deformations etc to get to your final object. The idea is that it’s a bit more flexible than your standard hierarchies.
> so i have a file but surely there is a better way…
yup, the way I retrieve the animation data is fairly simple, and I’ve finally got back to my usual machine so this time it’ll come with some source code…
First off I build up a very basic set of structures that hold all of the transforms in the scene by way of references (note, I never grab the data from the scene until the point at which I output it. The main reason is one of memory requirements. Maya is a bit memory hungry, and the scenes created by an artist can be huge in size, so I try to minimise memory overhead.)
the transform reference structure i use looks like this :
struct SXFormRef
{
SXFormRef() : parent(NULL),bUsed(false) {}
MDagPath path; // the path to the object through the dag hierarchy
bool bUsed; // flag to determine if this node is required for export
MString name; // the name of the transform
SXFormRef* parent; // pointer to the parent of the node
std::vector< SXFormRef* > children; // a list of this nodes children
std::vector< SShapeRef* > geometry; // a list of the geometry under this node
};
you may notice the array of geometries that can be children of the transforms, these can be lights, camera’s, meshes, curves etc, the structure I use for those are :
struct SShapeRef
{
SShapeRef() : bUsed(false) {}
MDagPath path; // path through the hierarchy to the node
MString name; // the maya name of the shape
bool bUsed; // is the node used.
};
I hold MDagPaths to the objects because these are guarenteed to be valid at any point during export.
After the writer function is called, I build up all of these references by walking through all the dependency nodes in the scene once and storing any known node type in one of four lists, transforms, shapes, materials and textures.
This looks a wee bit like :
MItDependencyNodes iter(MFn::kInvalid);
for(;!iter.isDone();iter.next()) {
MObject object = iter.item();
bool output = true;
if(object.hasFn(MFn::kDagNode)) {
MFnDagNode fnDAG(object);
if(fnDAG.isIntermediateObject())
{
output = false;
}
}
if(output) {
switch (object.apiType())
{
// if the node is a shape, ie, it has a parent transform
// then generate a SShapeRef node and add to the list
case MFn::kMesh:
{
MFnDagNode fnDag(object);
SShapeRef *pShape = new SShapeRef;
pShape->name = fnDag.name();
fnDag.getPath(pShape->path);
m_aShapeList.push_back(pShape);
}
break;
// if the node is a transform, then generate a SXFormRef
// structure in the list for this node. It will be checked
// to determine hierarchy information.later
case MFn::kTransform:
case MFn::kJoint:
{
MFnTransform fnTransform(object);
SXFormRef *pTransform = new SXFormRef;
fnTransform.getPath(pTransform->path);
pTransform->name = fnTransform.name();
m_aSceneTree.push_back(pTransform);
}
break;
default:
break;
}
}
This is slightly cut down a bit cos I grab a load of extra things like lattice, blend shapes etc…
Once all data nodes have been retrieved, I walk through all of my references and work out what is parented to what :
for(i=0;i<m_aSceneTree.size();i++)
{
MFnTransform fnTransform(m_aSceneTree[i]->path);
// work out if this node has a parent
if(fnTransform.parentCount() > 0) {
MFnDependencyNode fnParent(fnTransform.parent(0));
for( j=0; j < m_aSceneTree.size(); j++ ) {
if( fnParent.name() == m_aSceneTree[j]->name ) {
m_aSceneTree[i]->parent = m_aSceneTree[j];
break;
}
}
}
// process each of the children
unsigned int iChildCount = fnTransform.childCount();
for( j=0; j < iChildCount; j++ ) {
// if the child is a transform
if( fnTransform.child(j).hasFn(MFn::kTransform) ) {
MFnDependencyNode fnDep( fnTransform.child(j) );
// get the reference that we held for it
for( k=0; k < m_aSceneTree.size(); k++ )
{
if( m_aSceneTree[k]->name == fnDep.name() )
m_aSceneTree[i]->children.push_back(m_aSceneTree[k]);
}
}
// if not a transform it must be a shape so see if we have it
else {
MFnDependencyNode fnDep(fnTransform.child(j));
// get the reference that we held for it
for( k = 0; k < m_aShapeList.size(); k++ ) {
if( m_aShapeList[k]->name == fnDep.name() )
m_aSceneTree[i]->geometry.push_back(m_aShapeList[k]);
}
}
}
}
having built up the hierachies, I walk through the data I’ve recovered and flag which nodes I want to export. This usually involves pulling out the default Maya camera’s and removing transforms that have no effect (hence the bUsed flags).
I tend to grab the animation as hermite curves for specific attributes, using the following function :
//////////////////////////////////////////////////////////////////////////////
// proc : rglExport: utputAnimCurve()
// params : node -
// attribute - the attribute to output
// returns : true if output ok
// notes : queries the values of the attributes and locates an animation
// curve if present.
//////////////////////////////////////////////////////////////////////////////
bool rglExport: utputAnimCurve(MDagPath &node,const char *attribute)
{
MFnDependencyNode fnDep(node.node());
// identation for ascii export
//
MString tabs = "";
for(unsigned int i=0;i<level;i++)
{
tabs += " ";
}
// find the attribute plug
//
MPlug Attr = fnDep.findPlug(attribute);
// find any connections to the attribute
//
MPlugArray attrConnections;
Attr.connectedTo(attrConnections,true,true);
MFnAnimCurve fnAnim;
unsigned int iKeyCount = 0;
// find an animation curve.
//
for(i=0;i<attrConnections.length();i++)
{
if( attrConnections[i].node().apiType() == MFn::kAnimCurveTimeToAngular | |
attrConnections[i].node().apiType() == MFn::kAnimCurveTimeToDistance | |
attrConnections[i].node().apiType() == MFn::kAnimCurveTimeToTime | |
attrConnections[i].node().apiType() == MFn::kAnimCurveTimeToUnitless | |
attrConnections[i].node().apiType() == MFn::kAnimCurveUnitlessToAngular | |
attrConnections[i].node().apiType() == MFn::kAnimCurveUnitlessToDistance | |
attrConnections[i].node().apiType() == MFn::kAnimCurveUnitlessToTime | |
attrConnections[i].node().apiType() == MFn::kAnimCurveUnitlessToUnitless )
{
fnAnim.setObject( attrConnections[i].node() );
iKeyCount = fnAnim.numKeys();
break;
}
}
// get the current value of the attribute
//
float fValue;
Attr.getValue(fValue);
// output data
//
if(m_bAscii)
{
fprintf(m_File," %s%s VAL %f KEYS %d",tabs.asChar(),attribute,fValue,iKeyCount);
// output the keyframe information (if any exists)
//
for(unsigned int j=0;j<iKeyCount;j++)
{
float time = fnAnim.time(j).as(MTime::kSeconds);
float value = fnAnim.value(j);
float inTangent,outTangent,tmp;
fnAnim.getTangent(j,tmp,inTangent,true);
fnAnim.getTangent(j,tmp,outTangent,false);
fprintf(m_File," VAL %f TIME %f IN %f OUT %f",
value,time,inTangent,outTangent);
}
fprintf(m_File,"
“);
}
else
{
fprintf(m_File,” %s%s VAL %f KEYS %d",tabs.asChar(),attribute,fValue,iKeyCount);
for(unsigned int j=0;j<iKeyCount;j++)
{
float time = fnAnim.time(j).as(MTime::kSeconds);
float value = fnAnim.value(j);
float inTangent,outTangent,tmp;
fnAnim.getTangent(j,tmp,inTangent,true);
fnAnim.getTangent(j,tmp,outTangent,false);
fprintf(m_File," VAL %f TIME %f IN %f OUT %f",
value,time,inTangent,outTangent);
}
}
return true;
}
all that then remains is to traverse the transform hierarchy I’ve created, query each node type, and use the related function set to output the data to a file…
Hopefully that should get you started …
> hopefully i won’t need a recursive algorithm to read the file data. probably will.
very difficult to avoid…
>if i can’t work out an inverse kinamatics algorithm i will have to get animation data somehow.
IK chains comprise of three types, IK_Roots, IK_Effectors and IK_Joints. Should be able to grab that data from Maya or XSI files also…
Ask if you have any other questions…