Monday, 15 May 2017

Soft IK Constraint

The Maya IK Solver is a rigging staple but it does have a few shortcomings. Among them is the angle between the two bones appearing to snap when getting close to zero. This can negatively affect animation and it's not easy to manage.
In comes Soft IK.
It's not a new concept but one worth some investigation especially as far as I am aware it is still not part of core Maya. This node piggy backs on the standard IK Solver basically tracking the position of a driving transform but applying a function curve over the top that gradually attenuates the movement based on an input value. When looking into this I found a great site that showed a solution that had been worked on for XSI. Check this out here.
As ever I have included a python implementation of the node to try out with some support code.

Soft_IK_Constraint from SBGrover on Vimeo.

Python plugin

 import maya.cmds as cmds  
 import maya.OpenMaya as OpenMaya  
 import maya.OpenMayaMPx as omMPx  
 import math  
   
 kPluginNodeTypeName = "SoftIKConstraint"  
 kPluginNodeClassify = 'utility/general'  
 kPluginNodeId = OpenMaya.MTypeId(0x81081)  
   
  
 class SoftIKConstraint(omMPx.MPxConstraint):   
   
      matA = OpenMaya.MObject()  
      matB = OpenMaya.MObject()  
      matC = OpenMaya.MObject()  
      matInv = OpenMaya.MObject()  
      soft = OpenMaya.MObject()  
      output = OpenMaya.MObject()  
      pointX = OpenMaya.MObject()  
      pointY = OpenMaya.MObject()  
      pointZ = OpenMaya.MObject()  
      magRetrieved=0  
      chainLength=0  
      #e=2.71828  
        
      def __init__(self):  
   
           omMPx.MPxConstraint.__init__(self)  
             
      def compute(self,plug,data):  
             
           if plug == SoftIKConstraint.output:  
   
                softVal = data.inputValue(SoftIKConstraint.soft).asFloat()  
                inverseWorldMatrix = data.inputValue(SoftIKConstraint.matInv).asMatrix()  
                transformAWorldMatrix = data.inputValue(SoftIKConstraint.matA).asMatrix()  
                transformBWorldMatrix = data.inputValue(SoftIKConstraint.matB).asMatrix()  
                       
                vecAwm = OpenMaya.MPoint() * transformAWorldMatrix  
                vecBwm = OpenMaya.MPoint() * transformBWorldMatrix  
                vecAB = vecAwm-vecBwm  
                currentLength=getMagnitude(vecAB)  
                  
                if self.magRetrieved == 0:  
                     transformCWorldMatrix = data.inputValue(SoftIKConstraint.matC).asMatrix()  
                     vecCwm = OpenMaya.MPoint() * transformCWorldMatrix  
                     vecAC = vecAwm-vecCwm  
                     vecCB = vecCwm-vecBwm  
                     self.chainLength = getMagnitude(vecAC) + getMagnitude(vecCB)   
                     self.magRetrieved = 1  
                  
                if softVal == 0:  
                     ratioOfLength=1  
                else:  
                     affectedLength=self.chainLength*softVal  
                     unnafectedLength=self.chainLength-affectedLength  
                       
                     #create a falloff based on where the unnafected length ends. The function creates a curve all the way from 0 to max length but the result is forced to become linear where the unnafected length applies.  
                     if currentLength <= unnafectedLength:  
                          fractionOfLength = currentLength  
                     else:  
                          fractionOfLength = affectedLength*(1-math.exp((unnafectedLength - currentLength)/affectedLength))+unnafectedLength       
                     ratioOfLength = fractionOfLength / currentLength  
                worldPosition = ((vecAwm * (1.0 - ratioOfLength)) + OpenMaya.MVector(vecBwm * ratioOfLength))  
                localPosition = worldPosition * inverseWorldMatrix  
   
                outHandle = data.outputValue(SoftIKConstraint.output)  
                outHandleX = outHandle.child(SoftIKConstraint.pointX)  
                outHandleY = outHandle.child(SoftIKConstraint.pointY)  
                outHandleZ = outHandle.child(SoftIKConstraint.pointZ)  
                outHandleX.setMDistance(OpenMaya.MDistance(localPosition.x))  
                outHandleY.setMDistance(OpenMaya.MDistance(localPosition.y))  
                outHandleZ.setMDistance(OpenMaya.MDistance(localPosition.z))  
   
                data.setClean(plug)  
           else:  
                return OpenMaya.kUnknownParameter  
   
 def getMagnitude(vector):  
      magnitude=math.sqrt((vector.x*vector.x) + (vector.y*vector.y) + (vector.z*vector.z))  
      return magnitude  
        
 def nodeCreator():  
   
      return omMPx.asMPxPtr(SoftIKConstraint())  
        
 def nodeInitializer():   
        
      inputMatrix = OpenMaya.MFnMatrixAttribute()  
      softAttr = OpenMaya.MFnNumericAttribute()  
      outputAttr = OpenMaya.MFnUnitAttribute()  
      compoundOutputAttr = OpenMaya.MFnNumericAttribute()  
   
      SoftIKConstraint.matB = inputMatrix.create("HandleDriverWorldMatrix","hcwm")  
      inputMatrix.setHidden(1)  
      SoftIKConstraint.matA = inputMatrix.create("ChainDriverWorldMatrix","cdwm")  
      inputMatrix.setHidden(1)  
      SoftIKConstraint.matC = inputMatrix.create("MiddleJointWorldMatrix","mjwm")  
      inputMatrix.setHidden(1)  
      SoftIKConstraint.matInv = inputMatrix.create("HandleParentInverseMatrix","hpim")  
      inputMatrix.setHidden(1)  
      SoftIKConstraint.soft = softAttr.create("SoftIKValue","sik",OpenMaya.MFnNumericData.kFloat,0)  
      softAttr.setMin(0)  
      softAttr.setMax(1)  
      softAttr.setChannelBox(1)  
        
      #scene scale independent output  
      SoftIKConstraint.pointX = outputAttr.create("outputX", "outx",OpenMaya.MFnUnitAttribute.kDistance,0)  
      outputAttr.setWritable(0)  
      SoftIKConstraint.pointY = outputAttr.create("outputY", "outy",OpenMaya.MFnUnitAttribute.kDistance,0)  
      outputAttr.setWritable(0)  
      SoftIKConstraint.pointZ = outputAttr.create("outputZ", "outz",OpenMaya.MFnUnitAttribute.kDistance,0)  
      outputAttr.setWritable(0)  
      SoftIKConstraint.output = compoundOutputAttr.create("Output", "out",SoftIKConstraint.pointX,SoftIKConstraint.pointY,SoftIKConstraint.pointZ)  
      compoundOutputAttr.setWritable(0)  
      compoundOutputAttr.setHidden(1)    
   
      SoftIKConstraint.addAttribute(SoftIKConstraint.matA)  
      SoftIKConstraint.addAttribute(SoftIKConstraint.matB)  
      SoftIKConstraint.addAttribute(SoftIKConstraint.matC)  
      SoftIKConstraint.addAttribute(SoftIKConstraint.matInv)  
      SoftIKConstraint.addAttribute(SoftIKConstraint.soft)  
      SoftIKConstraint.addAttribute(SoftIKConstraint.output)  
        
      SoftIKConstraint.attributeAffects(SoftIKConstraint.matA,SoftIKConstraint.output)  
      SoftIKConstraint.attributeAffects(SoftIKConstraint.matB,SoftIKConstraint.output)  
      SoftIKConstraint.attributeAffects(SoftIKConstraint.matC,SoftIKConstraint.output)  
      SoftIKConstraint.attributeAffects(SoftIKConstraint.matInv,SoftIKConstraint.output)  
      SoftIKConstraint.attributeAffects(SoftIKConstraint.soft,SoftIKConstraint.output)  
   
 def initializePlugin(mobject):  
      print "> Initialising SoftIK Plugin"  
      fnPlugin = omMPx.MFnPlugin(mobject)  
      fnPlugin.registerNode(kPluginNodeTypeName,kPluginNodeId,nodeCreator,nodeInitializer,omMPx.MPxNode.kDependNode,kPluginNodeClassify)  
        
 def uninitializePlugin(mobject):  
      print "> Uninitialising SoftIK Plugin"  
      fnPlugin = omMPx.MFnPlugin(mobject)  
      fnPlugin.deregisterNode(kPluginNodeId)  

Python helper code

 # working on a scene in cm  
 import maya.cmds as mc  
 shoulder = mc.joint(n="shoulder", p=(-3,0,0))  
 elbow = mc.joint(n="elbow", p=(0,0,1))  
 wrist = mc.joint(n="wrist", p=(3,0,0))  
 mc.xform(mc.group(mc.circle(n="shoulder_ctrl"),w=True, n="shoulder_ctrl_grp"), ws=True, t=(-3,0,0))  
 mc.xform(mc.group(mc.circle(n="wrist_ctrl"),w=True, n="wrist_ctrl_grp"), ws=True, t=(3,0,0))  
 handle = mc.ikHandle(sj=shoulder, ee=wrist)[0]  
 mc.delete(mc.ls(type="softIKConstraint"))  
 mc.flushUndo()  
 mc.unloadPlugin("softIKConstraint")  
 mc.loadPlugin(r"softIKConstraint")  
 sik = mc.createNode("SoftIKConstraint")  
 mc.connectAttr("shoulder_ctrl.worldMatrix", sik + ".ChainDriverWorldMatrix")  
 mc.connectAttr(elbow + ".worldMatrix", sik + ".MiddleJointWorldMatrix")  
 mc.connectAttr("wrist_ctrl.worldMatrix", sik + ".HandleDriverWorldMatrix")  
 mc.connectAttr(handle + ".parentInverseMatrix", sik + ".HandleParentInverseMatrix")  
 mc.pointConstraint("shoulder_ctrl", shoulder)  
 mc.connectAttr(sik + ".Output", handle + ".translate")  

0 comments:

Post a Comment