4. gpExport - a Maya Exporter -- Extraction

This is clearly the biggest part of this document, as Maya is not very coder-friendly what extraction is concerned. And I'm not even going into details...
As a sidenote: Be aware that (AFAIK) any new plugin could break any (more sophisticated) exporter. In other words: one can never really finish an exporter, as there might come a new plugin, that forces an update of the exporter.

4.1 Mesh

Let's start with the struct that will contain the mesh's information:

55:     struct Mesh
56:     {
57:         std::vector<Material>       materials;
58:         std::string                 name;
59:         std::vector<MPoint>         positions;
60:         std::vector<MFloatVector>   normals;
61:         UVs                         uvs;
62:         SkinningData                skinningData;
63:         std::vector<Vertex>         vertices;
64:         std::vector<Int3>           indices;
65:     };

The obvious fields are name, vertices, indices and materials. Each vertex itself has 3 fields, resp. pointing to an entry in the positions, normals and uvs. The position-index is also used to index into the influences-array, which itself is stored in the skinningData.
Most of these entries will be explained in more detail later on.

The easiest way of getting all meshes is by iterating over them. The MItDag-class does just that. Here's an example (this time taken from meshExtraction.cpp):

395:         for (MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
396:              !dagIt.isDone();
397:              dagIt.next())
398:         {
399:             MDagPath currentMayaMeshPath;
400:             status = dagIt.getPath(currentMayaMeshPath);
401:             checkStatus("couldn't access path (dagIt.getPath)");
402: 
403:             if (!shouldMeshBeExtracted(currentMayaMeshPath))
404:             {
405:                 cout << "  ignoring "
406:                      << currentMayaMeshPath.partialPathName().asChar()
407:                      << endl;
408:                 continue;
409:             }
410: 
411:             Mesh currentMesh = extractMesh(currentMayaMeshPath);
412:             extractedMeshes.push_back(currentMesh);
413:         }
414:         return extractedMeshes;

MItDag iterates over all Maya-items. Depending on the parameters it does either a depth-first-, or breadth-first-search. The additional filter-parameter limits the search to the specified type (in this case MFn::kMesh).

We then just need to use the Mesh-function-set MFnMesh to access each of the found MDagPaths.

If the mesh is animated one should put the model into its correct frame (or its Bind-Pose).

4.1.1 Positions

The vertex positions is the most basic information one can extract out of Maya. As it is needed by Maya itself1, it is quite easy to access the position-information

Supposing the mesh is in its correct frame the position-extraction is a very short:

160:         MPointArray mayaPositions;
161:         status = mayaMesh.getPoints(mayaPositions, MSpace::kWorld);

4.1.2 Normals

Again. Extraction is quite easy. However, we don't know yet, how to join the positions and normals. (No: position[i] does not have normal[i] as normal). We'll need Vertex-information to do that.

179:         MFloatVectorArray mayaNormals;
180:         status = mayaMesh.getNormals(mayaNormals);

4.1.3 UVs

Every mesh can have several UV-sets, which are accessed by their name. Furthermore each UV-set is seperated in two arrays: the Us and the Vs.
Compared to the positions and normals it's slightly longer, but still very easy: We get all set-names, and iterate over them, extracting all UVs.

Seems to be the right moment to introduce the typedef, that will hold the UVs:

52:     typedef std::vector<std::vector<Float2> > UVs;

And now the code:

199:         MStringArray uvSetNames;
200:         status = mayaMesh.getUVSetNames(uvSetNames);
201:         checkStatus("couldn't extract UV-names");
202: 
203:         for (uint32 i = 0; i < uvSetNames.length(); ++i)
204:         {
205:             cout << "   uv-name: " << uvSetNames[i] << endl;
206:             
207:             MFloatArray us;
208:             MFloatArray vs;
209:             std::vector<Float2> currentUVSet;
210:         
211:             status = mayaMesh.getUVs(us, vs, &uvSetNames[i]);
212:             result.reserve(us.length());
213:             for (uint32 i = 0; i < us.length(); ++i)
214:             {
215:                 Float2 currentUV;
216:                 currentUV.u = us[i];
217:                 currentUV.v = vs[i];
218:                 currentUVSet.push_back(currentUV);
219:             }
220:             result.push_back(currentUVSet);
221:         }

4.1.4 Skinning-Data

In order to describe the skinning-extraction, I need to indroduce the structs, that will hold the data. (mesh.hpp):

22:     // the boneIndex references a joint in SkinningData::jointNames
23:     struct Influence
24:     {
25:         uint32  boneIndex; // points to a name in "SkinningData.jointNames"
26:         float   weight;
27:     };
28: 
29:     typedef std::vector<Influence> Influences;
30: 
31: 
32:     // for every vertex with positionIndex i the weights are in influences[i].
33:     // if there aren't any weights for the mesh, the influences-vector is of
34:     // size 0. (so don't index blindly into the vector).
35:     //
36:     // the influences index into the jointNames-vector, which might be
37:     // different for every mesh.
38:     struct SkinningData
39:     {
40:         std::vector<std::string>    jointNames;
41:         std::vector<Influences>     influences;
42:     };

The only not obvious field is the jointNames-vector. We need this vector, to achieve the mapping between animation-tracks and the influences: The name is used as identifier for each joint. Rather than putting the name into every influence, we just index into the jointNames-vector.

Once something touches animation, it starts getting difficult. And that's true for the skinning-data too. There's no easy way to get to the skin-cluster associated with the mesh. One could search the input-plugs and find the skin-cluster from there on. That's the way proposed by greggman on his site.

gpExport (as well as for instance MS's X-Exporter) takes an easier approach (which is less efficient): For every mesh we iterator over all dependency-nodes, filtering for skin-clusters. For every found skin-cluster, we look at all output-connections. If the current mesh is an output-shape of the skin-cluster, we found our skin-cluster.:

75:         for (MItDependencyNodes depNodeIt(MFn::kSkinClusterFilter);
76:              !depNodeIt.isDone();
77:              depNodeIt.next())
78:         {
79:             MObject depNodeObject = depNodeIt.item();
80:             MFnSkinCluster mayaSkinCluster(depNodeObject, &status);
81:             checkStatus("couldn't acces skinCluster");
82: 
83:             // normally we should cycle through all outputConnections,
84:             // and look, if it's our mesh.
85:             // the following line is just shorter
86:             uint32 shapeIndex =
87:                 mayaSkinCluster.indexForOutputShape(mayaOutputMeshPath.node(),
88:                                                     &status);
89:             if (!status)
90:                 continue; // our mesh is not an output for this skincluster

If a skin-cluster was found, we get all influence-object (should be joints), and store them in the jointNames-array.

100:             MDagPathArray influenceObjectPaths;
101:             mayaSkinCluster.influenceObjects(influenceObjectPaths, &status);
102:             checkStatus("couldn't get influenceObjects");
103:             for (uint32 i = 0; i < influenceObjectPaths.length(); ++i)
104:             {
105:                 skinningData.jointNames.push_back(
106:                     influenceObjectPaths[i].partialPathName().asChar());
107:             }

Then we get the influences:

111:             MObject mayaInputObject =
112:                 mayaSkinCluster.inputShapeAtIndex(shapeIndex, &status);
113:             // numVertices() should be the same for input and output shape.
114:             skinningData.influences.resize(mayaOutputMesh.numVertices());
115: 
116:             // iterate over all points (= components in Maya) and get the
117:             // influences (= [boneIndex, weight])
118:             uint32 pointCounter = 0;
119:             for (MItGeometry geometryIt(mayaInputObject);
120:                  !geometryIt.isDone();
121:                  geometryIt.next(), ++pointCounter)
122:             {
123:                 MObject mayaComponent = geometryIt.component();
124:                 MFloatArray mayaWeightArray;
125:                 uint32 numInfluences; // is going to be discarded
126: 
127:                 mayaSkinCluster.getWeights(mayaOutputMeshPath,
128:                                            mayaComponent,
129:                                            mayaWeightArray,
130:                                            numInfluences);

The important part is the call to getWeights of the skin-cluster. We iterate over all points (geometryIt) and get the skinweights with this function (one by one).
We then finally convert the retrieved weights into a convenient format:

133:                 for (uint32 j = 0; j < mayaWeightArray.length(); ++j)
134:                 {
135:                     // I know: we are working with floats, but Maya has a
136:                     // function to eliminate weights, that are too small
137:                     if (mayaWeightArray[j] != 0)
138:                     {
139:                         Influence influence;
140:                         influence.boneIndex = j;
141:                         influence.weight = mayaWeightArray[j];
142:                         skinningData.influences[pointCounter]
143:                             .push_back(influence);
144:                     }
145:                 }

4.1.5 Triangles and Vertices

Triangles and vertices are not immediate to obtain, but the extraction is straightforward. One has to pass by Maya's faces to get them. One correct way to do that: using MItMeshPolygon as in the following example:

360:         for (MItMeshPolygon polyIt(mayaMeshPath);
361:              !polyIt.isDone();
362:              polyIt.next())
363:         {
364:             std::vector<Int3> polyTriangles =
365:                 extractPolyTriangles(polyIt, vertexOffset);

4.1.5.1 Vertices

The positions, normals, etc. aren't yet useful, as we can't relate them. That's what the vertices are for. As already written, Maya stores its information in faces, and we need to pass by them. As we don't need the seperation into faces, we'll just merge all faces of a mesh.

The notion of "vertex" itself doesn't exist in Maya. Instead there are functions, to access all vertex-entries independantly (which makes sense, as not every vertex contains always the same entries). We are actually simplifying our life, by using always the same vertex-struct.

Here's finally the vertex-extraction method:

282:         unsigned int nbVertices = polyIt.polygonVertexCount();
283:         for (unsigned int i = 0; i < nbVertices; ++i)
284:         {
285:             Vertex currentVertex;
286:             if (doExtractPositions() || doExtractWeights())
287:                 currentVertex.positionIndex = polyIt.vertexIndex(i);
288: 
289:             if (doExtractNormals())
290:                 currentVertex.normalIndex = polyIt.normalIndex(i);
291: 
292:             if (doExtractUVs())
293:             {
294:                 MStringArray uvSetNames;
295:                 mayaMesh.getUVSetNames(uvSetNames);
296:                 // only the first set, for now.
297:                 polyIt.getUVIndex(i, currentVertex.uvIndex, &uvSetNames[0]);
298:             }

4.1.5.2 Triangles

Maya only triangulates individual faces (=polygons), but not complete meshes. What's slightly more annoying: Maya's triangulation returns indices for the positions. We want the indices to index into the face-relative vertices. After all, if we can't get from the triangles to the vertices, the vertices are pretty useless.

The extraction-method isn't very complicated (have a look at the code for even more comments):

238:         MPointArray nonTweakedPositions; // is going to be discarded here
239:         MIntArray mayaIndices; // that's what we are looking for
240:         polyIt.getTriangles(nonTweakedPositions, mayaIndices);
241:         MIntArray polyIndices;
242:         polyIt.getVertices(polyIndices);
243:         result = convertObjectToPoly(mayaIndices, polyIndices);
244:         // finally add the offset to each index
245:         for (unsigned int i = 0; i < result.size(); ++i)
246:         {
247:             Int3& currentTriangle = result[i];
248:             currentTriangle.i1 += vertexOffset;
249:             currentTriangle.i2 += vertexOffset;
250:             currentTriangle.i3 += vertexOffset;
251:         }

Note, that we are merging all faces (we won't be able to tell from what face a triangle came frome).

And now the conversion-method:

37:         uint32 i = 0;
38:         while (i < objectIndices.length())
39:         {
40:             Int3 currentTriangle;
41:             for (uint32 j = 0; j < 3; ++j)
42:             {
43:                 int32 indexTarget = objectIndices[i];
44:                 // search for the corresponding polyIndex
45:                 uint32 k;
46:                 for (k = 0; k < polyIndices.length(); ++k)
47:                 {
48:                     if (polyIndices[k] == indexTarget)
49:                         break;
50:                 }
51:                 if (k == polyIndices.length()) error("couldn't find poly-Index");
52:                 currentTriangle.i[j] = k;
53: 
54:                 i++;
55:             }
56:             triangles.push_back(currentTriangle);
57:         }

Basically we take every positionIndex (objectIndices[i]), and search the corresponding vertex, that has the same positionIndex. This should give a unique result (as positions are only used once per face).

4.1.6 Material

Although materials are shared between different meshes, gpExport currently creates new materials for every mesh (in other words: the materials are duplicated). However, it should be easy to join them again (if needed).

Materials are stored in shaders and the one uses getConnectedShaders on a mesh to get them (materialExtraction.cpp):

182:         mayaMesh.getConnectedShaders(instanceNumber,
183:                                      shaderSets,
184:                                      shaderVertexIndices);

Then we iterate over all shaders, and search for surfaceShader-plugs, as they are the nodes containing materials:

122:         MFnDependencyNode depNode(shaderSet);
123:         MPlug shaderPlug = depNode.findPlug("surfaceShader");
124:         
125:         if (shaderPlug.isNull()) return materials; // empty vector
126:         
127:         MPlugArray shaderPlugSources;
128:         shaderPlug.connectedTo(shaderPlugSources, true, false);

For each connected shader, we get the shader-node, and convert the contained material-information into a more convenient format (I never really tested the conversion, but all other exporters I've seen do it this way.) Furthermore we only extract phong-, blinn- and lambert-shaders:

134:             MPlug currentShaderPlugSource = shaderPlugSources[i];
135:             MObject shaderNode = currentShaderPlugSource.node();
136:             if (shaderNode.hasFn(MFn::kPhong))
137:                 fillMaterial<MFnPhongShader>(&currentMaterial,
138:                                              shaderNode,
139:                                              "phong");
140:             else if (shaderNode.hasFn(MFn::kBlinn))
141:                 fillMaterial<MFnBlinnShader>(&currentMaterial,
142:                                              shaderNode,
143:                                              "blinn");
144:             else if (shaderNode.hasFn(MFn::kLambert))
145:                 fillMaterial<MFnLambertShader>(&currentMaterial,
146:                                                shaderNode,
147:                                                "lambert");

 96:     MaterialExtractor::fillMaterial(Material* pMaterial,
 97:                                     const MObject& shaderNode,
 98:                                     const std::string& shadingType) const
 99:     {
100:         T shader(shaderNode);
101:         std::string shadingMode;
102:         if (shadingType == "phong")
103:             pMaterial->shadingMode = "phong";
104:         else // blinn and lambert
105:             pMaterial->shadingMode = "gouraud";
106:             
107:         pMaterial->ambient = shader.ambientColor();
108:         pMaterial->diffuse = shader.diffuseCoeff() * shader.color();
109:         pMaterial->selfIllumination = shader.incandescence();
110: 
111:         pMaterial->shininess = getShininess(shader);

getShininess is parametrized by the type, and returns an appropriate value for each shader-type.

Finally gpExport extracts the textures:

37:         MFnDependencyNode shaderDepNode(shaderPlugSource.node());
38:         
39:         for (MItDependencyGraph shaderGraphIt(shaderPlugSource,
40:                                               MFn::kFileTexture,
41:                                               MItDependencyGraph::kUpstream);
42:              !shaderGraphIt.isDone();
43:              shaderGraphIt.next())
44:         {
45:             MObject shaderTextureNode = shaderGraphIt.thisNode();
46:             MFnDependencyNode mayaTexture(shaderTextureNode);
47:             
48:             MString textureFile;
49:             mayaTexture.findPlug("fileTextureName").getValue(textureFile);


4.2 Animation

Animation is way more complicated than static information. One difficulty lies in the fact, that people need different animation-data. Some need the positions at every frame, some want skeleton-transformations at each frame, some want them just at key-frames, etc.

gpExport only extracts skeleton-keyframes, but the used methods should be applicable to other extractions. Also: the gpExport's extraction is not complete. I already found examples, where nothing got extracted... (feel free to submit patches;)

At Wootsoft we used the same animation for different skeletons, and the extraction is therefore completely split up (which is not really unreasonable anyways).

4.2.1 Bind-Pose

In order to export the mesh and skeleton correctly, the model needs to be put into its bind-pose first. Ideally the exporter should undo this modification:).

This should have been easy, but was one of the most difficult tasks of the exporter: there's no easy way to get the bind-pose matrix.

First thing to do, to go into bind-pose: turn the inverse kinematic off bindPoseTool.cpp:

132:         MIkSystem::setGlobalSnap(false);
133:         MIkSystem::setGlobalSolve(false);

We then run over all joints, find the bind-pose matrix, and put the joint into its initial transformation. If a joint is instanced, only the first instance is put back into bind-pose (which automatically puts back the others too). Also, we need to remember the current transformation, as we need to go back later.

152:             MTransformationMatrix currentTransform = joint.transformation(&status);
153: 
154:             joint2transform.first = jointPath.node();
155:             joint2transform.second = currentTransform;
156:             undoInfo.savedTransforms.push_back(joint2transform);
157: 
158:             MMatrix bindPoseMatrix = getBindPoseMatrix(joint);

Now the difficult task: getting the bind-pose matrix. There are several methods of doing it, and gpExport unfortunately uses one, that is not completly correct. For the correct way look at greggman's site (again).

We first find the bindPose-plug and all connected nodes:

61:         MPlug tempBindPosePlug = joint.findPlug("bindPose");
62: 
63:         MPlugArray mapConnections;
64:         tempBindPosePlug.connectedTo(mapConnections, false, true);

we then get the first found connection, search for the xformMatrix-attribute, and finally extract it:

78:         MPlug bindPosePlug = mapConnections[0];
79: 
80:         // this node should be a "dagPose"-node (in case you want to look it
81:         // up in the help)
82:         MFnDependencyNode bindPoseNode(bindPosePlug.node());
83: 
84:         // and as such, has the "xformMatrix"-attribute.
85:         MObject xformMatrixAttribute = bindPoseNode.attribute("xformMatrix");
86: 
87:         MPlug localTransformPlug(bindPosePlug.node(), xformMatrixAttribute);
88:         // xformMatrix is an array. to get our localmatrix we need to select
89:         // the same index, as our bindPosePlug (logicalIndex()).
90:         localTransformPlug.selectAncestorLogicalIndex(
91:                             bindPosePlug.logicalIndex(), xformMatrixAttribute);
92: 
93:         MObject localMatrixObject;
94:         localTransformPlug.getValue(localMatrixObject);

I don't know if this is really necessary, but it can't hurt: syncing the meshes:

105:         for (MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
106:              !dagIt.isDone();
107:              dagIt.next())
108:         {
109:             cout << ".";
110:             MDagPath meshPath;
111:             dagIt.getPath(meshPath);
112:             MFnMesh mesh(meshPath.node());
113:             mesh.syncObject();
114:         }

Undoing the information is way easier:

178:         for (BindPoseUndoInformation::JointMatrixVector::const_iterator
179:                it = undoInfo.savedTransforms.begin();
180:             it != undoInfo.savedTransforms.end();
181:             ++it)
182:         {
183:             cout << ".";
184:             status = MFnIkJoint(it->first).set(it->second);
185:             if (!status)
186:                 cout << "prob in undoInfo" << endl;
187:         }
188: 
189:         MIkSystem::setGlobalSnap(undoInfo.ikSnap);
190:         MIkSystem::setGlobalSolve(undoInfo.ikSolve);
191: 
192:         cout << endl;
193: 
194:         syncMeshes();

As gpExport already does all this for you, I advise using its implementation

4.2.2 Skeleton

Skeletons are essentially hierarchical transformations: a transformation-matrix and its children. In addition we store the joint-name, as we'll use it as identifier. Eventhough not necessary we keep the world-transfrom too (skeleton.hpp):

18:     struct JointNode
19:     {
20:         std::string             name;
21:         std::vector<JointNode>  children;
22:         MMatrix                 localTransform;
23: 
24:         // is not necessary (as we have the hierarchy,
25:         // and the localTransform).
26:         // only included to avoid recalculations.
27:         MMatrix                 worldTransform;
28:     };
29: 
30:     struct Skeleton
31:     {
32:         std::vector<JointNode> rootNodes;
33:     };

All sequent code-snippets of this subsection are taken from skeletonExtraction.cpp.

The extraction itself is not that difficult: for each root-joint, we recursively extract the hierarchy.

67:         for (MItDag dagIt(MItDag::kDepthFirst, MFn::kJoint, &status);
68:              !dagIt.isDone();
69:              dagIt.next())
70:         {
71:             MDagPath jointPath;
72:             dagIt.getPath(jointPath);
73:             if (shouldJointBeExtracted(jointPath))
74:                 skeleton.rootNodes.push_back(
75:                     extractSkeletonHierarchy(jointPath, 2));
76:             // children have already been treated by extractSkeletonHierarchy,
77:             // or are intentionally ignored
78:             dagIt.prune();

The extractSkeletonHierarchy-method first gets the joint-name. Then the local transformation, and the world transform. Finally it iterates over its children:

28:         currentJointNode.name = jointPath.partialPathName().asChar();
29: 
30:         MFnTransform transform(jointPath);
31:         currentJointNode.localTransform = transform.transformation().asMatrix();
32:         currentJointNode.worldTransform = jointPath.inclusiveMatrix();
33:         for (uint32 i = 0; i < jointPath.childCount(); ++i)
34:         {
35:             if (jointPath.child(i).hasFn(MFn::kJoint))
36:             {
37:                 MDagPath childPath;
38:                 childPath.set(jointPath);
39:                 childPath.push(jointPath.child(i));
40:                 if (!shouldJointBeExtracted(childPath))
41:                     continue; // prune tree
42:                 JointNode childJointNode = extractSkeletonHierarchy(childPath,
43:                                                                     depth + 1);
44:                 currentJointNode.children.push_back(childJointNode);

4.2.3 Tracks

gpExport gets the tracks (the keyframes and their transformations) for all joints. The struct holding this information has 3 additional fields: fps, start end end. fps obviously holds the frames per second. This also means, that some Maya-fps-settings can't be correctly extracted (for instance, all fps smaller the one). start and end are redundant fields, as the beginning and end of the animation can be found in the tracks, but often simplifies the output. (animation.hpp)

22:     struct KeyFrame
23:     {
24:         MTime   time;
25:         MMatrix transformation;
26:     };
27: 
28:     typedef std::vector<KeyFrame> Track;
29:     typedef std::map<std::string, Track> StringTrackMap;
30:     
31:     struct Animation
32:     {
33:         uint32              fps;
34:         MTime               start; // first keyframe. redundant information
35:                                    // (could be extracted out of the tracks)
36:         
37:         MTime               end;   // last keyframe. redundant information
38:                                    // (could be extracted out of the tracks)
39:         
40:         StringTrackMap      tracks;
41:     };

The extraction is done in three steps:

  • get all animated joints.
  • get the key-times for all joints.
  • and finally get the transformations.

The animated joints are quite easy to obtain:

60:         for (MItDag dagIt(MItDag::kDepthFirst, MFn::kJoint, &status);
61:              !dagIt.isDone();
62:              dagIt.next())
63:         {
64:             MDagPath jointPath;
65:             dagIt.getPath(jointPath);
66: 
67:             if (!MAnimUtil::isAnimated(jointPath))
68:             {
69:                 cout << "not animated" << endl;
70:                 continue;
71:             }
72: 
73:             jointPaths.push_back(jointPath);

The key-times aren't that difficult neither: for every animated joint we look for the animated plugs, get the MFnAnimCurves, which hold all the times, and extract them:

37:         MPlugArray animatedPlugs;
38:         MAnimUtil::findAnimatedPlugs(jointPath, animatedPlugs);
39:         for (unsigned int i = 0; i < animatedPlugs.length(); ++i)
40:         {
41:             MObjectArray curves;
42:             MAnimUtil::findAnimation(animatedPlugs[i], curves);
43:             for (unsigned int j = 0; j < curves.length(); ++j)
44:             {
45:                 MFnAnimCurve curve(curves[j]);
46:                 for (unsigned int k = 0; k < curve.numKeys(); ++k)
47:                     jointTimes.insert(curve.time(k));
48:             } // for curves
49:         } // for animatedPlugs

Then we create a times->joint map, so all times are sorted (and we know what joint is animated at each time). We need a multi-map here, as several joints are usually animated at the same time (animationExtraction.hpp/cpp):

24:     typedef std::multimap<MTime, uint32> Times2JointMap;

 87:         for (uint32 i = 0; i < animatedJointPaths.size(); ++i)
 88:         {
 89:             MDagPath jointPath = animatedJointPaths[i];
 90:             
 91:             std::set<MTime> jointTimes = extractKeyFrameTimes(jointPath);
 92:             
 93:             // the jointTimes-set contains all key-times this joint is 
 94:             // animated with.
 95:             for (std::set<MTime>::const_iterator it = jointTimes.begin();
 96:                  it != jointTimes.end();
 97:                  ++it)
 98:                 // insert pairs [time, {animatedJointPath}Index] into
 99:                 // the times2JointMap.
100:                 times2JointMap.insert(Times2JointMap::value_type(*it, i)); 
101:         }

Once all key-times have been extracted, we set Maya's animation-time to every time of this map, and get the transformations for every joint that is animated.

159:         // save initial time to go back after we have extracted the
160:         // transformations.
161:         const MTime initialTime = MAnimControl::currentTime();
162:         
163:         // mayaTime = time Maya is set to. (property stays true in the
164:         // following loop). I avoided naming it "currentTime", as i name
165:         // my loop-vars currentXX.
166:         MTime mayaTime = initialTime;
167: 
168:         // times2Joint is a multiset and hence sorted.
169:         for (Times2JointMap::const_iterator it = times2JointMap.begin();
170:              it != times2JointMap.end();
171:              ++it)
172:         {
173:             if (mayaTime != it->first)
174:             {
175:                 nbKeyTimes++; // just for logging
176:                 MAnimControl::setCurrentTime(it->first);
177:                 mayaTime = it->first;
178:             }
179:             Track&   currentTrack     = result.tracks[jointNames[it->second]];
180:             MDagPath currentJointPath = animatedJointPaths[it->second];
181:             KeyFrame newKeyFrame;
182:             newKeyFrame.time = it->first;
183:             // extract the transformation for this joint.
184:             MFnTransform transform(currentJointPath);
185:             newKeyFrame.transformation = transform.transformation().asMatrix();
186:             currentTrack.push_back(newKeyFrame);
187:         }
188:         // back to initial time
189:         MAnimControl::setCurrentTime(initialTime);





1: Maya doesn't need to know, what most plugins do, as long, as it receives the position at every given frame.

This Html page has been produced by Skribe.
Last update Sun Sep 5 10:51:21 2004.