Problem with modifying BindPose on skeleton

Hi friends

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();
        }
    }

Am I missing something ?
Any help is appreciated.

I provided a simple test code:

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 went through the source code of Skeleton and Bone, found nothing special. I’m curious about the answer.

Thanks for your interest @yan

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.

Searched all the forum and found only [this post] (Bone local coordinate system - #2 by Momoko_Fan)

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.

2 Likes

Thanks for reply @sgold

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.

How does bind pose differ from “first pose”?

I mean it’s first bind pose (before I set the new bind pose from Beta).

1 Like

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 :

You mean that BVHUtils.reTarget() isn’t correctly converting your root-bone translations?

If Jade and Beta have identical rigs/skeletons, you shouldn’t need BVHUtils for retargeting. Just copy animations from one AnimControl to the other.

1 Like