Wednesday, 6 December 2017

Animation Ghosting Locator

I have played around with locators before in Maya. I have produced a couple that have actually been of some use in the daily grind. During the transition to Maya 2016 from previous versions I had to convert these to run with the new drawOverride method that was required to display these in viewport 2.0. I found it a headache as the new way of assembling the data seemed rather opaque especially compared to the simpler Legacy Viewport days.
More recently I decided to return to this. Now that I have a better knowledge of the API and am more confident using C++ I wanted to understand it the process a more completely.
The new classes available to me now include MUIDrawManager which actually encapsulates the tools needed to create the visual aspects of the locators.

I had opted to create a locator that would provide the animation team with some kind of ghosting on the meshes in the scene to allow them to compare between the current frame and others without changing the frame they are on. For this I identified a number of things I would need.

1. A way of returning the mesh at a different frame
2. A way of rendering that returned data in the current view
3. Options to change the visual aspects as required
4. Options to change the frame read
5. An option to have the result track the current frame with the existing offset

I was already aware of a class that would give me the ability to read items at a different frame from the current one. MDGContext. I had never used it before but doing so did not prove difficult. The sample code below shows the section that deals with the read ahead for the ghosting data.

void GhostingNodeDrawOverride::getMeshPoints(const MDagPath& objPath)
 MStatus status;
 MObject GhostObj = objPath.node(&status);

 if (status)
  // get plug to mesh
  MPlug plug(GhostObj, GhostingNode::mesh);
  // if data exists do something
  if (!plug.isNull())
   // get user input data (desired frame/tracking switch)
   double frame = getFrame(objPath);
   int isTracking = getTracking(objPath);
   // is tracking on for the first time?
   if (isTracking == 1 && switched == 0)
    // get current frame
    MTime currentTime = MAnimControl::currentTime();
    // returns the current time as NTSC
    int cur_time = (int);
    // calculate the difference between desired frame and current frame
    difference = (int)cur_time - (int)frame;
    switched = 1;

   // or is tracking already on? 
   else if(isTracking == 1) 
    // get current frame
    MTime currentTime = MAnimControl::currentTime();
    // return the current time as NTSC
    int cur_time = (int);
    // calculate offset based on previously calculated difference
    frame = cur_time - difference;
    // convert the calculated frame to NTSC
    MTime desiredFrame(frame, MTime::kNTSCFrame);
    // set up an MDGContext to the desired frame
    MDGContext prevCtx(desiredFrame);
    // get MObject from plug at given context
    MObject geoPrev = plug.asMObject(prevCtx);
    // create MeshPolygon iterator from MObject
    MItMeshPolygon meshIt(geoPrev);
   // else everything is off
    switched = 0;
    difference = 0;

Something to bear in mind is that making use of a context based read can be expensive. Maya has to re-evaluate a frame under the hood to pass back the correct data which can slow down performance somewhat.

Items 3, 4 and 5 are easily dealt with as they are simply extra attributes on the node that give a user options as to how to interact with it.
Item 2 involved the actual rendering of the locator which involved finding out how a number the MPxDrawOverride functions operate with each other. Amongst them a key function is addUIDrawables in which the locator is 'built'. Most of your code will sit in this function as it is where you will assemble points, edges, colors etc, and package them ready to be rendered. A large bulk of locator code, at least in my case sits under the override

For my locator I wanted to allow a user to draw a whole mesh, a wireframe and a set of points either one at a time or altogether. I also wanted to allow control over colour and transparency which would be very important to distinguish the locator in a busy scene. Below is the section of code where this is set up. I have previously created a point list, edge list and color list from the iterator in the code snippet above and am passing them to MUIDrawManager functions to get the expected result.

if (trianglePointList.length() > 0)

 // get the color of the points
 MColor pointColor = colorWeights[0];

 // rest and start drawing

 //user defined display settings
 // render edges
 if (displaySettings[2] == 1)
  drawManager.mesh(MHWRender::MUIDrawManager::kLines, edgePointList, NULL, NULL, NULL, NULL);

 // render faces
 if (displaySettings[1] == 1)
  drawManager.mesh(MHWRender::MUIDrawManager::kTriangles, trianglePointList, NULL, &colorWeights, NULL, NULL);

 // render points
 if (displaySettings[0] == 1)
  drawManager.points(trianglePointList, false); //second parameter is draw in 2d

 // finish drawing for now

As you can see from above I am using a mixture of kLines, kTriangles and just points. The first two I supply to the mesh function and come from MUIDrawManager::Primitive.

Even though I was initially a little put off with the way Maya now needs the code structured for Viewport 2.0 drawing of locators it did not take long to get a grasp on how the class should be utilised and although I am sure I still have much to learn I feel a good start has been made.

Maya Animation Ghosting Locator from SBGrover on Vimeo.


Post a Comment