weightRemapper Node

Unlike in a skinCluster-based system if you use a translateUVN deformer you'll need to define which maps are left/right pairs for each other. To do this we just need:

  1. An inputWeightList attribute

  2. An outputWeightList attribute (these attributes need to have useArrayDataBuilder set to true so that they update properly)

  3. A the left and right vertex pairing for each shape

  4. An internal system for pairing the input and output shape plugs.

Those first two requirements can be done with no problem, but the third requirement is where it's easy to misstep.


Precalculating or Recalculating?


One solution is to have the node take a mesh as an input, and then internally generate the left/right pairing. This is a pretty user friendly way of doing things since any less-technical artists that want to build a component using the new node don't need to fuss around with creating the vertex-pairing themselves. The issue with using that solution is that the vertex pairing needs to be recalculated every time you open the scene, and each unique weightRemapper node has to calculate (and store) the pairing independently. C++ is fast though, so maybe the difference in speed is worth the ease-of-use.


To test the speed I created a single mesh with 5,000 verts which had two clusters connected by a weightRemapper node. The weightRemapper node had two modes controlled with a boolean attribute, one mode used a precomputed intArray pairing to flip the weight map, and the other mode computed that same intArray internally using an undeformed duplicate of the mesh as an input.


The precomputed method added .006 seconds to the file load time.

The method that computed the intArray from the mesh added .32 seconds to the file load time.


Now a more dense mesh would make that difference even greater, based on my testing a 10,000 vert mesh would add 1.256 seconds to the file load time. If we have 50 weightRemapper nodes (which is a reasonable ballpark estimate) we'll be adding more than a minute to the scene load time. That's already too much in my opinion, especially when you consider that 10,000 verts is nowhere near the limit, you could have multiple meshes, and you could have more than 50 weightRemapper nodes.


So what originally seemed like the best solution is actually going to cost us time. So instead we should precompute the pairing for each mesh we intend to deform and pass it into the weightRemapper node.


Generating the Vertex Pairing


Because the vertex pairing will only be computed once and the code we use to generate it is not part of the compiled C++ node, we don't need to be too concerned with the optimization of the code. Below is a very simple example.

import maya.api.OpenMaya as om2
import maya.api.OpenMayaAnim as omAnim2
import time 

s_time = time.time()

sel = om2.MSelectionList()
sel.add('pSphereShape1')
shape_mObj = sel.getDependNode(0)

mit = om2.MItGeometry(shape_mObj)
mit_second = om2.MItGeometry(shape_mObj)
pairing = om2.MIntArray()

while not mit.isDone () :
    src_index = mit.index()
    src_pos = mit.position()
    closest_index = -1
    closest_distance = 100
    mit_second.reset()
    while not mit_second.isDone () :
        dest_pos = mit_second.position()
        dest_pos[0] *= -1
        distance = dest_pos.distanceTo(src_pos)
        if distance < closest_distance:
            closest_distance = distance
            closest_index = mit_second.index()
            if closest_distance < .001:
                break
            
        mit_second.next()
    
    pairing.append(closest_index)        
    mit.next()

print time.time() - s_time

What you'll notice about the above code is that the intArray it produces is not:

left_id, right_id, left_id, right_id

Instead the first element is the id of the vertex that is paired with vtx[0], and the secon element is the id of the vertex that is paired with vtx[1]. This makes the intArray half as long but just as good. This does of course mean that one source_id cannot be paired to multiple destination_ids, we have to have a 1 to 1 pairing but for our purposes this doesn't matter.


Using the Pairing:


So we've got our inputWeightList and our vertex pairing inside of our node now, how are we going to create the outputWeightList? This is another place where it's easy to misstep. We could do something like this:

for i in range(intArray.length()):
    get the input weight at element "i"
    make that the new value of outputWeights[intArray[i]]

Which would work but why are we setting every single element on the outputWeights? Seems a bit wasteful. We only need to remap the non-default weights from the inputWeights. Something kind of like this:

//for each component
for (unsigned i = 0; i < inputWeights_handle.elementCount(); i++, inputWeights_handle.next())
{
	//get the input weights index and value
	unsigned comp_index = inputWeights_handle.elementIndex();
	if (comp_index > remapArray_val_mIntArray.length() - 1)
	{
		continue;
	}
	//status = inputWeights_handle.jumpToElement(remapArray_val_mIntArray[comp_index]);
	float inputWeight = 1;
	if (status == MS::kSuccess)
	{
		inputWeight = inputWeights_handle.inputValue().asFloat();
	}
	//outputWeights
	MDataHandle weightElement = outputBuilder.addElement(remapArray_val_mIntArray[comp_index]);
	weightElement.setFloat(inputWeight);
}

Notice how we are using in inputWeights_handle.next() to only loop through the non-default weights that exist, instead of using jumpToElement(i) which would get us every the weight value for every component. If your deformerSet is 100 times smaller than the overall mesh then the size of the array you output will be reduce by 100 times.



We still have not addressed issue number 4 "An internal system for pairing the input and output shape plugs". This is a very annoying thing to get working in Maya so it will be the topic of a future blog post.

Featured Posts
Recent Posts