Between mid-October and November, the content on AREA will be relocated to the Autodesk Community M&E Hub and the Autodesk Community Gallery. Learn more HERE.
In the MPxSkinCluster code sample the code that demonstrate standard skinning implementation is wrong. Overall there are issues due to a misuse of physical and logical indices of the matrix and skin weights attributes.
First the code sample assumes that influence transforms (usually joints) are always connected to the attribute MPxSkinCluster::matrix in a consecutive manner which is wrong since you can have:
skin_cluster.matrix: joint1, joint2, joint3
logical index: 1, 3, 4
physical index: 0, 1, 2
Similarly it is assumed that the logical index 'j' of "weightList[i].weight[j]" is the same as the physical index, which leads to bugs when joints are not connected in a consecutive manner or if "weightList[i].weight[j]" is sparse.
Here is the faulty version with some annotations:
for ( ; !iter.isDone(); iter.next()) {
MPoint pt = iter.position();
MPoint skinned;
MArrayDataHandle weightsHandle = weightListHandle.inputValue().child( weights );
// wrong the size "numTransforms" can change from vertex to vertex
// and should be fetched dynamicaly with weightsHandle.elementCount();
for ( int i=0; i<numTransforms; ++i ) {
// 'i' here is the physical index of the transform
// jumpToElement() should be used with a logical index!
if ( MS::kSuccess == weightsHandle.jumpToElement( i ) ) {
skinned += ( pt * transforms[i] ) * weightsHandle.inputValue().asDouble();
}
}
iter.setPosition( skinned );
weightListHandle.next();
}
The correct code tested on various models and Maya 2020:
MStatus MyCustomSkinCluster::deform(MDataBlock& block,
MItGeometry& iter,
const MMatrix& worldMat,
unsigned int /*multiIndex*/)
{
// get the influence transforms
MArrayDataHandle transformsHandle = block.inputArrayValue(matrix);
int nb_joints = transformsHandle.elementCount();
if (nb_joints == 0) {
return MS::kSuccess;
}
MArrayDataHandle bindHandle = block.inputArrayValue(bindPreMatrix);
// LBS
MArrayDataHandle weightListHandle = block.inputArrayValue( weightList );
if ( weightListHandle.elementCount() == 0 ) {
// no weights - nothing to do
return MS::kSuccess;
}
const MMatrix worldMatInverse = worldMat.inverse();
for ( iter.reset(); !iter.isDone(); iter.next())
{
// In this fix we also support meshes associated to a transform other
// than identity:
MPoint pt = iter.position() * worldMat;
MPoint skinned;
// get the weights for this point
MArrayDataHandle weightsHandle = weightListHandle.inputValue().child( weights );
int nb_weights = weightsHandle.elementCount();
for (int i = 0; i < nb_weights; i++)
{
weightsHandle.jumpToArrayElement(i);
double w = weightsHandle.inputValue().asDouble();
// logical index represent the actuall joint index
int joint_idx = weightsHandle.elementIndex();
transformsHandle.jumpToElement( joint_idx ); // Jump to logical index
MMatrix mat = MFnMatrixData(transformsHandle.inputValue().data()).matrix();
bindHandle.jumpToElement( joint_idx ); // Jump to logical index
MMatrix preBindMatrix = MFnMatrixData( bindHandle.inputValue().data() ).matrix();
mat = preBindMatrix * mat;
skinned += ( pt * mat ) * w;
}
// Set the final position.
iter.setPosition( skinned * skinnedWorldMatInverse );
// advance the weight list handle
weightListHandle.next();
}
return MS::kSuccess;
}
Can't find what you're looking for? Ask the community or share your knowledge.