Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
46 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Extracting Vertices

So let's write a quick function to find all the vertices in a model. We will assume that we are interested in extracting all the vertices in the file:

void extractVertices()
{
   // we assume here that Maya has been initialized and the file in
   // question has already been loaded.

   MStatus stat;
   MItDag dagIter( MItDag::kBreadthFirst, MFn::kInvalid, &stat );

   for ( ; !dagIter.isDone(); dagIter.next())
   {
      MDagPath dagPath;
      stat = dagIter.getPath( dagPath );

      if ( stat )
      {
         MFnDagNode dagNode( dagPath, &stat );

         // this object cannot be intermediate, and it must be a mesh
         // and it can't be a transform.
         // Intermediate objects are special meshes
         // that are not drawn used for mesh morphs or something.
         if ( dagNode.isIntermediateObject()) continue;
         if ( !dagPath.hasFn( MFn::kMesh )) continue;
         if ( dagPath.hasFn( MFn::kTransform )) continue;

         MFnMesh fnMesh( dagPath );

         // get the vertices that are part of the current mesh
         MPointArray vertexList;
         fnMesh.getPoints( vertexList, MSpace::kWorld );

         // iterate through all the vertices
         for ( u32 i = 0; i < vertexlist.length(); i++ )
         {
            vertexlist[i].cartesianize();
            mpoint point = vertexlist[i];

            // here is your data... now go do whatever you want with
            // it. if you need a unique identifier for this vertex,
            // use it's index in the mesh, and some kind of mesh id.
            // these stay constant while exporting ( so long as the file is
            // not edited )
            processvertex( point.x, point.y, point.z );
         }
      }
   }
}

The code is pretty self explanatory. dagIter iterates through all the nodes in the DAG, where your vertices are. Everytime you find a node that supports the MFn::kMesh type, and is not a transform, or an intermediate node, then you use it to make an MFnMesh object, and extract vertices. Notice when go to get the vertices themselves, you request what space you want them represented in. MSpace::kWorld will convert the vertex into world space for you ( by multiplying the vertex by all the transforms from the root of the DAG on down ). You can also ask it to give you the vertices in any number of local spaces if you are extracting skeletal data as well.

Extracting Polygons

Extracting polygons is similarly straight-forward:

void extractPolygons()
{
   MStatus stat;
   MItDag dagIter( MItDag::kBreadthFirst, MFn::kInvalid, &stat );

   for ( ; !dagIter.isDone(); dagIter.next())
   {
      MDagPath dagPath;
      stat = dagIter.getPath( dagPath );

      if ( stat )
      {
         MFnDagNode dagNode( dagPath, &stat );

         if ( dagNode.isIntermediateObject()) continue;
         if ( !dagPath.hasFn( MFn::kMesh )) continue;
         if ( dagPath.hasFn( MFn::kTransform )) continue;

         // get the mesh and all its vertices
         MFnMesh fnMesh( dagPath );
         MPointArray vertexList;
         fnMesh.getPoints( vertexList, MSpace::kWorld );

         // now iterate over all the polygons in the mesh
         MItMeshPolygon piter( dagPath, comp );
         for ( ; !piter.isDone(); piter.next())
         {
            // for each polygon you can get the indices of
            // each of its vertices in the vertex list above
            MIntArray vertexIdxs;
            piter.getVertices( vertexIdxs );

            if ( vertexIdxs.length() == 3 )
            {
               // process a triangle
               MPoint point0 = vertexList[vertexIdxs[0]];
               MPoint point1 = vertexList[vertexIdxs[1]];
               MPoint point2 = vertexList[vertexIdxs[2]];

               processTriangle( point0, point1, point2 );
            }
         }
      }
   }
}

From this basic code you can get a great deal of other information. You can query each vertex for light and texture coordinate information for example. What you can't easily get this way are the textures themselves, or the color of each material. This stuff is not in the DAG and must be extracted another way. In Maya all mesh elements can be associated with a material. The material can be something very simple, like a single lambert shaded color, or something much more complex, like a hierarchy of materials that include various light models, textures, transparencies etc. It can get pretty complicated and I haven't needed to understand all the intricacies.

Extracting Materials

Getting a single material associated with a polygon face is a little trickier than the code above but still not too bad. The trick is that this time you can't just iterate over the DAG to find the polygons, but rather you have to iterate over the whole DG to find all the materials first, and then use the materials to find all the polygons each material is associated with.

bool extractMaterials()
{
   MStatus stat;
   MItDag dagIter( MItDag::kBreadthFirst, MFn::kInvalid, &stat );

   for ( ; !dagIter.isDone(); dagIter.next())
   {
      MDagPath dagPath;
      stat = dagIter.getPath( dagPath );

      if ( stat )
      {
         MFnDagNode dagNode( dagPath, &stat );

         if ( dagNode.isIntermediateObject()) continue;
         if ( !dagPath.hasFn( MFn::kMesh )) continue;
         if ( dagPath.hasFn( MFn::kTransform )) continue;

         MFnMesh fnMesh( dagPath );

         // Here is the trick, get all the nodes connected to this
         // mesh, whether they are on the DAG or not. This will
         // include all the materials.
         unsigned instanceNumber = dagPath.instanceNumber();
         MObjectArray sets;
         MObjectArray comps;

         fnMesh.getConnectedSetsAndMembers( instanceNumber, sets,
                                               comps, true );

         // iterate over all the connected sets and look for materials
         { for ( u32 i = 0; i < sets.length(); i++ )
         {
            mobject set = sets[i];
            mobject comp = comps[i];

            mfnset fnset( set );
            mfndependencynode dnset( set );

            mobject ssattr = dnset.attribute( mstring( "surfaceShader" ) );

            mplug ssplug( set, ssattr );

            mplugarray srcplugarray;
            ssplug.connectedto( srcplugarray, true, false );

            if ( srcplugarray.length() == 0 ) continue;

            // this object contains a reference to a shader, or
            // material, so we might call our own function to write
            // that material to our own data structure for later export,
            // and return a material index so we can attach that index
            // to all the polygons we are going to extract below.
            mobject srcnode = srcplugarray[0].node();
            u32 matidx = makematerial( srcnode );

            // we might want to stop processing this node right now if
            // it is a material our exporter does not support
            if ( material_is_not_supported( matidx )) continue;

            // otherwise, let's iterate over all the polygons that
            // are colored with this material
            mitmeshpolygon piter( dagpath, comp );
            for ( ; !piter.isdone(); piter.next())
            {
               mintarray vertexidxs;
               piter.getvertices( vertexidxs );

               // jump to our own code to export a polygon with a material
               writepolygon( vertexidxs, matidx );
            }
         }
      }
   }
}

Extracting information from a material node itself is pretty simple. For example let's strip out all the information for a phong shader node:

void makeMaterial( MObject& srcNode )
{
   if ( srcNode.hasFn( MFn::kPhong ))
   {
      MFnPhongShader phong( srcNode );

      cerr << "Found phong shader: \""
           << phong.name().asChar() << "\" << endl;

      // extract all the phong parameters:
      MColor glowColor     = phong.incandescence();
      MColor diffuseColor  = phong.color() * phong.diffuseCoeff();
      MColor specularColor = phong.specularColor();
      Double cosinePower   = phong.cosPower();
      MColor transColor    = phong.transparency();

      // now build a material, and write it out..
   }
}

Extracting Animation Data

Getting animation information out of Maya involves a little trickery also. Essentially you want to extract information out of the DG at a given time, and then you want to alter the time parameter for each frame. There are several problems. First is how to determine where the keyframes are. Your data will either

  • have been animated in Maya with keyframes carefully set up by your animator for each game frame, in which case you just need to find the time for each keyframe.

  • Or the data has been animated without keyframes, and you only need to evaluate it at a the desired frequency ( e.g. 60 fps ) over the length of the animation.

  • Another way of course is that you will be modeling motion in your game in a way similar to how Maya does it and you just want to extract Maya's own data representations for motion directly ( for example you may just want to grab the motion's actual spline parameters ).
  • I can only help you with the first two ways. The following code will find all the key frames, and the length of an animation:

    void findFrames()
    {
       MItDag dagIter( MItDag::kBreadthFirst, MFn::kInvalid, &stat );
       MTime maxKeyTime = 0;
    
       for ( ; !dagIter.isDone(); dagIter.next())
       {
          MDagPath dagPath;
          stat = dagIter.getPath( dagPath );
    
          MFnDagNode dn( dagPath );
    
          // find all the motion nodes
          MItDependencyGraph dgIter( dagPath.node(),
                                     MFn::kAnimCurve,
                                     MItDependencyGraph::kUpstream,
                                     MItDependencyGraph::kBreadthFirst,
                                     MItDependencyGraph::kNodeLevel,
                                     &stat );
          if ( stat )
          {
             // get all the keyframe times
             for ( ; !dgIter.isDone(); dgIter.next())
             {
                MObject anim = dgIter.thisNode( &stat );
                MFnAnimCurve animCurve( anim, &stat );
    
                // found some keyframe information
                if ( MS::kSuccess == stat )
                {
                   u32 numKeys = animCurve.numKeyframes( &stat );
                   for ( u32 currKey = 0; currKey < numkeys; currkey++ )
                   {
                      // truncating values here...
                      mtime keytime = animcurve.time( currkey, &stat );
    
                      // store this key frame time somewhere.
                      storekeytime( keytime );
    
                      // and store the maximum frame time
                      if ( time > maxKeyTime )
                         maxKeyTime = time;
                   }
                }
             }
          }
       }
    }
    

    maxKeyTime hold the last keyframe time, and the length of an animation. storeKeyTime is a call to your own code to store a given keyframe time so you can evaluate the animation at that time later. Remember that probably many duplicate times will be inserted into that array. There are quite likely many MFnAnimCurve nodes in the DG with the same keyframe times to animate different parts of the model. You can either chose to use the keyframe times as they are stored, or you can chose to use the last maxKeyTime and iterate over fixed steps from 0 to that time. This is something you should probably discuss with those people responsible for making the animations.

    The last part of the problem is how to evaluate a model at a given time, to extract all the vertices, or transforms for a given animation. For this you use the handy MAnimControl object, like so:

    void processAnimationFrame( MTime& time )
    {
       // create a Dag iterator
       MStatus stat;
       MItDag dagIter( MItDag::kBreadthFirst, MFn::kInvalid, &stat );
    
       // here is the magic animation time setting object. Set it to the
       // desired time, and presto...
       MAnimControl animctrl;
       animctrl.setCurrentTime( time );
    
       for ( ; !dagIter.isDone(); dagIter.next())
       {
          MDagPath dagPath;
          stat = dagIter.getPath( dagPath );
    
          if ( stat )
          {
             MFnDagNode dagNode( dagPath, &stat );
    
             if ( dagNode.isIntermediateObject()) continue;
             if ( !dagPath.hasFn( MFn::kMesh )) continue;
             if ( dagPath.hasFn( MFn::kTransform )) continue;
    
             // your mesh object... and it should be
             MFnMesh fnMesh( dagPath );
    
             // get the vertices that are part of the current mesh
             MPointArray vertexList;
             fnMesh.getPoints( vertexList, MSpace::kWorld );
    
             // iterate through all the vertices
             for ( u32 i = 0; i < vertexlist.length(); i++ )
             {
                vertexlist[i].cartesianize();
                mpoint point = vertexlist[i];
    
                // here is your data, but this time it should be different
                // for each time at which you evaluate it. you should be
                // able to get tranform information over time in a similar
                // way.
                processvertex( point.x, point.y, point.z );
             }
          }
       }
    }
    

    And there you have it, everything you need to know to extract basic model information out of Maya.

    I have not found very many Maya API programming tutorials on the net. There is at least one really great site with Maya programming tutorials run by Bryan Ewert

    Maya is a Registered Trademark of Alias|wavefront.


    Contents
      Page 1
      Page 2

      Printable version
      Discuss this article