I am having problem with modifying bind pose of skeleton. Everything works OK until I call the setBindingPose(); on skeleton to save the bind pose. (as javadoc mentioned it here and here)
but it does not work for me. This video shows the result I am getting :
(I am copying bone bind pose for Arm and Hand from the character in the right to the character in the left)
Here is the relevant code :
Copy bind pose from source skeleton to target skeleton (Only copy the rotation) : (Works fine)
public static void copyBindPose(Skeleton source, Skeleton target, String sourceBoneName, String targetBoneName) {
if (source != null && target != null) {
Bone sourceBone = source.getBone(sourceBoneName);
Bone targetBone = target.getBone(targetBoneName);
targetBone.setBindTransforms(targetBone.getBindPosition(), sourceBone.getBindRotation(), targetBone.getBindScale());
}
}
Save binding pose : (doesn’t work well !!)
public void saveBindPose() {
if (targetSkel != null) {
targetSkel.updateWorldVectors();
targetSkel.setBindingPose();
}
}
public class TestBindPoseDeform extends SimpleApplication {
SkeletonControl sourceSkelControl;
SkeletonControl targetSkelControl;
@Override
public void simpleInitApp() {
assetManager.registerLoader(XbufLoader.class, "xbuf");
//Load spatials
Spatial source = assetManager.loadModel("Character/beta.j3o");
Spatial target = assetManager.loadModel("Character/jade.xbuf");
//Get SkeletonControls
sourceSkelControl = AnimUtils.getSkeletonControll(source);
targetSkelControl = AnimUtils.getSkeletonControll(target);
//copy bind pose (T-Pose) from source skeleton to target skeleton for arm bones
//Works fine
copyPose("arm_stretch.l");
copyPose("arm_stretch.r");
//Saves the current skeleton state as it's binding pose. NOT WORKIN !
// after calling setBindingPose() the bones return to their initial pose (like in the video above)
targetSkelControl.getSkeleton().updateWorldVectors();
targetSkelControl.getSkeleton().setBindingPose();
//attach target spatial to scene
rootNode.attachChild(target);
/**
* A white, directional light source
*/
DirectionalLight sun = new DirectionalLight();
sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
sun.setColor(ColorRGBA.White.mult(3));
rootNode.addLight(sun);
}
private void copyPose(String boneName) {
Bone sourceBone = sourceSkelControl.getSkeleton().getBone(boneName);
Bone targetBone = targetSkelControl.getSkeleton().getBone(boneName);
targetBone.setBindTransforms(targetBone.getBindPosition(), sourceBone.getBindRotation(), targetBone.getBindScale());
}
public static void main(String[] args) {
new TestBindPoseDeform().start();
}
}
I tested both with HardwareSkining and SoftwareSkining, removed SkeletonControl and AnimControl from spatial and recreated them, tested with both jme 3.1 and 3.2 snapshot no difference.
Its not enough to just call setBindTransforms(), you have to make sure the inverse world bind transforms are computed as well. When you use the Skeleton(Bone) constructor, it will do that for you based on the bind transforms that the bones currently have set. Hence, if you modify them after using the constructor, you have to call update() and then setBindingPose() on the skeleton after you do that.
This is the whole code for setBindingPose() :
/**
* Saves the current bone state as its binding pose, including its children.
*/
void setBindingPose() {
bindPos.set(localPos);
bindRot.set(localRot);
bindScale.set(localScale);
if (modelBindInversePos == null) {
modelBindInversePos = new Vector3f();
modelBindInverseRot = new Quaternion();
modelBindInverseScale = new Vector3f();
}
// Save inverse derived position/scale/orientation, used for calculate offset transform later
modelBindInversePos.set(modelPos);
modelBindInversePos.negateLocal();
modelBindInverseRot.set(modelRot);
modelBindInverseRot.inverseLocal();
modelBindInverseScale.set(Vector3f.UNIT_XYZ);
modelBindInverseScale.divideLocal(modelScale);
for (Bone b : children) {
b.setBindingPose();
}
}
right. As Bone#setBindingPose() and Bone#update() are not public, that’s why I have to use Skeleton#updateWorldVectors() and Skeleton#setBindPose().
/**
* Updates world transforms for all bones in this skeleton.
* Typically called after setting local animation transforms.
*/
public void updateWorldVectors() {
for (int i = rootBones.length - 1; i >= 0; i--) {
rootBones[i].update();
}
}
/**
* Saves the current skeleton state as it's binding pose.
*/
public void setBindingPose() {
for (int i = rootBones.length - 1; i >= 0; i--) {
rootBones[i].setBindingPose();
}
}
In general, a bone does not have a defined length or direction in JME. What it does have is a coordinate system. (In fact it has several, one of which is its bind-pose coordinate system.) That system is specified relative to the coordinate system of its parent bone. Simply copying the arm-bone rotation from one model to another only works if the parent bones use similar coordinate systems.
I haven’t looked at how Jade and Beta are constructed. It’s possible that (in T pose, say) the Beta’s elbow lies in the X axis (in Beta’s shoulder space) while Jade’s elbow (in the same pose) lies on the Z axis (in Jade’s shoulder space). In that case, copying the bind rotation from Beta to Jade will result in a different bind pose, with Jade’s upper arm bone twisted 90 degrees from T pose.
Both of the characters have same rig structure. I auto rigged them using this blender add-on Auto-Rig Pro.
Actually I want have all my animations saved on Beta model, then at runtime I will get the animations from Beta and will directly set them on my characters.
Copying bind pose (I only modify bind rotation) works just fine and animation plays OK . The problem is when I call this setBindingPose() method bones return to their previous bind pose, (as you see in video).
Not sure why this happens.
For now I just decided to not save bind pose and every thing should work just fine. I just need to re-copy bind pose every time I load the model.
In case you are curious why I am using this way instead of retargetting using BVHUtils, it’s because I can have more natural animation on my characters.
take a look at this video for better understanding what I mean :