|
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.
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).
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);
|
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);
|
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: }
|
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).
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>(¤tMaterial,
138: shaderNode,
139: "phong");
140: else if (shaderNode.hasFn(MFn::kBlinn))
141: fillMaterial<MFnBlinnShader>(¤tMaterial,
142: shaderNode,
143: "blinn");
144: else if (shaderNode.hasFn(MFn::kLambert))
145: fillMaterial<MFnLambertShader>(¤tMaterial,
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);
|
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).
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
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);
|
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.
|