Monday, 1 May 2017

Speed Node

Some time ago our animation team was working on a scene with a chase that included multiple vehicles that had to move at constantly changing speeds. As we were working within an environment that effected real world values the speeds of the vehicles were of utmost importance.
I had already played with custom locators and nodes in Maya and thought that an interesting task to set myself would be to create a locator that could feed back a speed based on distance covered over a frame taking into account the frame rate.
It transpired that the challenge here was not so much getting the calculations to work in a proper fashion so that the node would output the correct speeds but rather getting the locator to work in both legacy mode and viewport 2.0. We use viewport 2.0 because amongst other things its more advanced rendering capabilities allow us to visualise our shaders in a proper fashion. It was challenging taking the relatively straightforward commands that construct items for the legacy viewport and converting them so that they were also compatible with viewport 2.0. Autodesk does provide some background on this in their documentation here. Ultimately this was really due to my inexperience with the Maya API, C++, viewport 2.0 and parallel evaluation all of which in some fashion shaped my journey to completing this node. For what is a relatively simple node it felt like a lot of work.
I will hopefully add a post on the custom locators I have created in the future. Although the final node is compiled in C++, I have included some Python code to give any aspiring individual a little bit of help into how the node works. This code does not create any locator, instead outputting to an annotation node. It is also important to note that it will only work in DG mode. The support code will switch to this or you can do this manually.

SpeedNode from SBGrover on Vimeo.

Python Node
 import sys  
 import maya.OpenMaya as OpenMaya  
 import maya.OpenMayaAnim as OpenMayaAnim  
 import maya.OpenMayaMPx as OpenMayaMPx  
 import math  
 import maya.cmds as mc  
   
   
 class SpeedNode(OpenMayaMPx.MPxNode):  
     
   id = OpenMaya.MTypeId( 0x80117 )  
   drawDbClassification = 'utility/general'  
   drawRegistrantId = "SpeedNode"  
     
   worldMatrix = OpenMaya.MObject()  
   unitType = OpenMaya.MObject()  
   output = OpenMaya.MObject()  
   fpsSwitch = OpenMaya.MObject()  
   name = OpenMaya.MObject()  
   time = OpenMayaAnim.MAnimControl()  
   position = OpenMaya.MPoint()  
   oldPosition = OpenMaya.MPoint()  
   speed = 0  
   oldTime = 0  
   speedType = 'mph'  
   fps = 0.0  
   fpsLib = {5:15,6:24,7:25,8:30,9:48,10:50,11:60}  
   string = ""  
   
   def __init__(self):  
     OpenMayaMPx.MPxNode.__init__(self)  
   
   def compute(self, plug, data):  
   
     inputWorldMatrix = OpenMaya.MMatrix()  
     inputWorldMatrix = data.inputValue(SpeedNode.worldMatrix).asMatrix()  
     self.position.x, self.position.y, self.position.z = inputWorldMatrix(3,0), inputWorldMatrix(3,1), inputWorldMatrix(3,2)  
   
     if plug == SpeedNode.output:  
       fpstime=OpenMaya.MTime()  
       self.fps = self.fpsLib[fpstime.unit()]  
       unit = data.inputValue(SpeedNode.unitType).asShort()  
       inputName = data.inputValue(SpeedNode.name).asString()  
       showFps = data.inputValue(SpeedNode.fpsSwitch).asInt()  
       delta = self.position - self.oldPosition  
       magnitude = math.sqrt((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z))  
       self.oldPosition = OpenMaya.MPoint(self.position)  
       curTime = self.time.currentTime().value()  
         
       if magnitude != 0 and (curTime - self.oldTime) != 0:  
         self.speed = magnitude / ((curTime - self.oldTime) / self.fps)  
         
       else:  
         self.speed = magnitude  
         
       #mph  
       if unit == 0:  
         #conversion factor for seconds to hour is *3600  
         #conversion factor for cm to miles is *160000  
         if self.speed != 0:  
           self.speed = self.speed*3600/160000  
         self.speedType = 'mph'  
         
       #kph  
       if unit == 1:  
         #conversion factor for seconds to hour is *3600  
         #conversion factor for cm to kilometres is *100000  
         if self.speed != 0:  
           self.speed = self.speed*3600/100000  
         self.speedType = 'kph'  
           
       #mps  
       if unit == 2:  
         #conversion factor for seconds to seconds is 1  
         #conversion factor for cm to metres is *100  
         if self.speed != 0:  
           self.speed = self.speed/100  
         self.speedType = 'mps'  
         
       self.oldTime = curTime  
         
       if showFps:  
         self.string = (inputName+" : %.2f " + self.speedType + " @" + str(self.fps)+ "fps") %abs(self.speed)  
       else:  
         self.string = (inputName+" : %.2f " + self.speedType) %abs(self.speed)  
             
       outputData = data.outputValue(plug)  
       outputData.setString(self.string)  
   
       data.setClean(plug)  
         
 def initialize():  
   matrixAttr = OpenMaya.MFnMatrixAttribute()  
   typedAttr = OpenMaya.MFnTypedAttribute()  
   enumAttr = OpenMaya.MFnEnumAttribute()  
   numericAttr = OpenMaya.MFnNumericAttribute()  
   
   SpeedNode.worldMatrix = matrixAttr.create("inputWorldMatrix","iwm")  
     
   SpeedNode.name = typedAttr.create("name","n",OpenMaya.MFnData.kString)  
     
   SpeedNode.output = typedAttr.create("output","o",OpenMaya.MFnData.kString)  
     
   SpeedNode.unitType = enumAttr.create("unitType","ut")    
   enumAttr.addField("mph",0)  
   enumAttr.addField("kph",1)  
   enumAttr.addField("mps",2)  
   enumAttr.channelBox = True  
     
   SpeedNode.fpsSwitch = numericAttr.create("showFPS","sfps",OpenMaya.MFnNumericData.kBoolean)  
   numericAttr.channelBox = True  
     
   SpeedNode.addAttribute(SpeedNode.unitType)  
   SpeedNode.addAttribute(SpeedNode.fpsSwitch)  
   SpeedNode.addAttribute(SpeedNode.name)  
   SpeedNode.addAttribute(SpeedNode.worldMatrix)  
   SpeedNode.addAttribute(SpeedNode.output)  
   
   SpeedNode.attributeAffects(SpeedNode.fpsSwitch,SpeedNode.output)  
   SpeedNode.attributeAffects(SpeedNode.name,SpeedNode.output)  
   SpeedNode.attributeAffects(SpeedNode.worldMatrix,SpeedNode.output)  
   SpeedNode.attributeAffects(SpeedNode.unitType,SpeedNode.output)  
   
   
 def nodeCreator():  
   return OpenMayaMPx.asMPxPtr(SpeedNode())        
   
 def initializePlugin(obj):  
   plugin = OpenMayaMPx.MFnPlugin(obj)  
   
   try:  
     plugin.registerNode("SpeedNode", SpeedNode.id, nodeCreator, initialize, OpenMayaMPx.MPxNode.kDependNode, SpeedNode.drawDbClassification)  
   except:  
     sys.stderr.write("Failed to register node\n")  
     raise  
   
 def uninitializePlugin(obj):  
   plugin = OpenMayaMPx.MFnPlugin(obj)  
   
   try:  
     plugin.deregisterNode(SpeedNode.id)  
   except:  
     sys.stderr.write("Failed to deregister node\n")  
     pass  
   
   
   

Helper Code

 import maya.cmds as mc  
   
 mc.delete(mc.ls(type="SpeedNode"))  
 mc.flushUndo()  
 mc.unloadPlugin("SpeedNode.py")  
 mc.loadPlugin("SpeedNode.py")  
   
   
 def createSpeedNode():  
   
   mc.evaluationManager( mode="off" )  
   if not mc.pluginInfo('SpeedNode.py', q=True, l=True): mc.loadPlugin('SpeedNode.py')  
   
   sel = mc.ls(sl=True)  
   if sel:  
     for i in sel:  
       newNode = mc.createNode('SpeedNode')  
       mc.connectAttr(i + '.worldMatrix', newNode + '.inputWorldMatrix')  
       ann_node = mc.createNode('annotationShape')  
       mc.parent(ann_node, i)  
       mc.connectAttr(newNode + ".output", ann_node + ".text" )  
   
   else:  
     mc.error("Select Moving Transform(s)")  

0 comments:

Post a Comment