Archive for September, 2014

Delta mush in Splice

Because all the cool kids are into it, here’s an (early, unoptimised, naive, fairly gnarly) implementation of Delta Mush in Fabric Splice. It’s a regular splice node, not a deformer (still sorting that out).

deltaMush
It takes you from the world’s worst skinning job (on the left), to something that is slightly less awful (on the right).

Tangent space matrix code from Roy Nieterau, relax operator from Phil Taylor.

Update: 9/9/2014 – smoothPos now weights points by distance – end effect is you need fewer smooth iterations.
Update 11/9/2014 – Deltas are now simple vectors. Smooth function now better and faster thanks to exceedingly kind help from Roy Nieterau

require Math;
require Geometry;


operator smoothPos<<<index>>>(Vec3 inPositions[], io Vec3 outPositions[], PolygonMesh mesh) {
 
  Vec3 result = inPositions[ index ];
 
  LocalL16UInt32Array surroundingPoints;
  mesh.getPointSurroundingPoints( index, false, surroundingPoints );
  UInt32 nbNei = surroundingPoints.size();
 
  if( nbNei ) {
 
    Scalar neiSumDistance = 0;
    Vec3 delta;
   
    Scalar neiDistance;
    Vec3 toNeighbour;
    for( UInt32 i = 0; i < nbNei; ++i ) {
      UInt32 neiPt = surroundingPoints.get(i);
     
      toNeighbour = inPositions[ neiPt ] - result; // vector to neighbour
      neiDistance = toNeighbour.length();
     
      delta += toNeighbour * neiDistance; // weight by distance
      neiSumDistance += neiDistance;
    }
   
    delta /= (neiSumDistance * nbNei); // divide by sum of all neighbour distances (weighted) and amount of neighbours (average: mean)
    result += delta;
  }
 
  outPositions[index] = result;
}



operator relax_operator(in Scalar factor, in Integer iterations, io PolygonMesh mesh0) {

  GeometryAttributes attributes = mesh0.getAttributes();
  Ref<Vec3Attribute> positionsAttr = attributes.getPositions();
  Vec3 positions1[] = positionsAttr.values;
  Vec3 positions2[] = positions1.clone();

  for(Index i = 0; i < iterations; i++){
    smoothPos<<<mesh0.pointCount()>>>(positions1, positions2, mesh0);
    if(i < iterations-1){
      swap(positions1, positions2);
      positionsAttr.values = positions1;
    }
  }
  positionsAttr.values = positions2;
  //positionsAttr.incrementVersion();
  
  mesh0.recomputePointNormals();
  Ref<Vec3Attribute> normalsAttr = mesh0.getNormals();
  normalsAttr.incrementVersion();

  
  mesh0.recomputeTangents();

}


function Mat44 tangentSpaceMatrix(PolygonMesh mesh, Index point)
{
  //Roy Nieterau

  GeometryAttributes attributes = mesh.getAttributes();
  Ref<Vec4Attribute> tangentsAttr = attributes.getAttribute("tangents", Vec4Attribute);
  Vec4 tangents[] = tangentsAttr.values;

  Vec3 pos = mesh.getPointPosition(point);
  
   Vec3 normal = mesh.getPointNormal(point);
    Vec3 tangent = tangents[point].toVec3();
    Vec3 binormal = normal.cross(tangent);

      
    Mat44 mat(tangent.x, binormal.x, normal.x, pos.x,
              tangent.y, binormal.y, normal.y, pos.y,
              tangent.z, binormal.z, normal.z, pos.z,
              0       , 0        , 0         , 1    );
         
    return mat;
   
}




operator initialOffest<<<index>>>(in PolygonMesh refMesh, in PolygonMesh refMeshsmooth, 
            io PolygonMesh outMesh, io Vec3 delta[]) {
              
  Vec3 srcPos = refMesh.getPointPosition(index);
  Vec3 dstPos = refMeshsmooth.getPointPosition(index);

  Vec3 d = srcPos - dstPos;
  Mat44 srcMat = tangentSpaceMatrix(refMeshsmooth, index);
  srcMat.setTranslation(Vec3(0,0,0));

  delta[index] = srcMat.inverse() * d;
  
}

operator deformTask<<<index>>>(in PolygonMesh inMesh, io PolygonMesh outMesh,  io Vec3 delta[], in Scalar factor) {

  Vec3 oPos = inMesh.getPointPosition(index);
  Vec3 pos = outMesh.getPointPosition(index);

  Mat44 mat = tangentSpaceMatrix(outMesh, index);

  Vec3 newVec =   mat * delta[index];
  
  outMesh.setPointPosition(index, oPos.linearInterpolate(newVec, factor));

}



operator deltaMush(in PolygonMesh inMesh, io PolygonMesh outMesh, in PolygonMesh refMesh, 
                   io PolygonMesh refMeshSmooth, in Integer smoothIterations, in Boolean reset,
                   io Boolean init,
                   in Scalar factor, io Vec3 delta[]) {
  
 
  UInt64 start = getCurrentTicks();
  // validation
  if (smoothIterations < 1) {
    outMesh = inMesh.clone();
    outMesh.recomputePointNormals();
    setError("need some iterations");
    return;
  }
  
  
  if (reset == true) {
    init = false;
  }
  
  
  outMesh = inMesh.clone();
  outMesh.recomputePointNormals();
  if (factor > 0) {
    relax_operator(1.0, smoothIterations, outMesh);


    if(init == false) {
      delta.resize(outMesh.pointCount());
      refMeshSmooth = refMesh.clone();
      relax_operator(1.0, smoothIterations, refMeshSmooth);

      initialOffest<<<refMeshSmooth.pointCount()>>>(refMesh, refMeshSmooth, outMesh, delta);
    
      init = true;
    }


    deformTask<<<outMesh.pointCount()>>>(inMesh, outMesh, delta, Math_clamp(factor, 0, 1));
    outMesh.recomputePointNormals();
  
  }
  //outMesh = refMeshSmooth;
  
  
  UInt64 end = getCurrentTicks();
  //report("Elapsed time: " + getSecondsBetweenTicks(start, end) + " seconds");
}

Local timewarps for animated objects

Sure, scene timewarp is cool, but what if you want to just timewarp a few objects (say, your tracked camera)??

Select the objects that you want to timewarp, and run this script. Any animation curves on your selection will have a new animCurve node plugged into their “time” input, that you can tweak to timewarp your animation.


def makeWarpCurve():
    timeWarpNode = cmds.createNode('animCurveTT', name='timeWarper1')
    startTime= cmds.playbackOptions( q=True, min=True)
    endTime= cmds.playbackOptions( q=True, max=True)
    
    cmds.setAttr(timeWarpNode + '.preInfinity', 1)
    cmds.setAttr(timeWarpNode + '.postInfinity', 1)

    cmds.addAttr(timeWarpNode, ln='isTimeWarp',  at='bool')

    cmds.setKeyframe(timeWarpNode, time=(startTime,), insert=True)
    cmds.setKeyframe(timeWarpNode, time=(endTime,), insert=True)
    cmds.keyframe(timeWarpNode, index=(0,), absolute=True, valueChange=startTime)
    cmds.keyframe(timeWarpNode, index=(1,), absolute=True, valueChange=endTime)
    cmds.keyTangent(timeWarpNode, index=(0,), itt='linear', ott='linear')
    cmds.keyTangent(timeWarpNode, index=(1,), itt='linear', ott='linear')
    
    return timeWarpNode


def applyTimeWarp(objects, warpCurve=None):
    if not warpCurve:
        warpCurve = makeWarpCurve()
    else:
        if not cmds.objExists(warpCurve):
            print 'Cannot find warpCurve: "%s"' % warpCurve
            return
        
    for obj in objects:
        print obj
        for conn in cmds.listConnections( obj, d=False, s=True, type='animCurve'):
            print '\t', conn
            cmds.connectAttr(warpCurve + '.output', conn + '.input',  force=True)
    
    return warpCurve

def selectAllWarpNodes():
    wn = getAllWarpNodes()
    if wn:
        cmds.select(wn)
    else:
        print 'No warp nodes found'
    

def getAllWarpNodes():
    warpNodes = cmds.ls('*.isTimeWarp', recursive=True, objectsOnly=True)
    return warpNodes
    
    
def warpSelected():
    sel  = cmds.ls(sl=True)
    if sel:
        warpCurve = applyTimeWarp(sel)
        cmds.select(warpCurve)

warpSelected()

You can have multiple timewarps in a scene. Just select each object (or group of objects) separately and make new timewarpers.

I have no idea how it interacts with scene timewarp. Probably the universe will implode on itself.

the “getAllWarpNodes()” function will return all timewarper nodes in the scene. Or do a “selectAllWarpNodes()” to select them all.

Nodes missing from the hypershade

If you create a shader node via scripting without specifying “asShader=True” (there may be gui ways of getting the same effect), you may find that said nodes don’t appear in the top hypershade tabs. Maddening.

There’s no need to nuke the nodes and start again – they can be fixed in place. There’s a number of hidden sets that the hypershade tabs rely upon, so it’s trivial to reconnect them. Code below – select the offending nodes, and run this.

import maya.cmds as cmds

def fixShadingNodes(inNode):
    target = None
    
    nodeType = cmds.nodeType(inNode)
    if cmds.getClassification(nodeType, satisfies="shader"):
        target = 'defaultShaderList1.shaders'
    elif cmds.getClassification(nodeType, satisfies="texture"):
        target = 'defaultTextureList1.textures'
    elif cmds.getClassification(nodeType, satisfies="utility"):
        target = 'defaultRenderUtilityList1.utilities'
    
    if target:
        cmds.connectAttr(sel + '.message', target, na=True, force=True)

for sel in cmds.ls(sl=True):
    fixShadingNodes(sel)
    

Or apply it to all instances of a node type

for x in cmds.ls(type='aiImage'):
    fixShadingNodes(x)

Fabric splice – push op

Okay, let’s get this party started. Fabric Splice is freakin’ awesome and any studio can get 50 licenses for free.

Here’s the simplest possible deformer that i can possibly think of – the good old “push” operator from softimage. It moves vertices along the point normal.

require Math;
require Geometry;

operator doPushOp<<<index>>>(in Scalar amplitude, io PolygonMesh mesh0){
  Vec3 pos = mesh0.getPointPosition(index);
  Vec3 n  = mesh0.getPointNormal(index);
  
  Vec3 newPos = pos + (n * amplitude);
  mesh0.setPointPosition(index, newPos);
  
}

operator pushop(in Scalar amplitude, io PolygonMesh meshes[]) {
  for (Size i=0; i<meshes.size(); i++) {
    meshes[i].recomputePointNormals();
    doPushOp<<<meshes[i].pointCount()>>>(amplitude, meshes[i]);
    }

}
Return top