Programatically create skeleton in Three.js

1.2k views Asked by At

I am loading a model of a mechanism (e.g. a robot arm) in Three.js. Sadly the models I am using don't have a skeleton, but I have the locations, axes and so on of the joints. In order to use e.g. inverse kinematic solvers like Three-IK, I want to create a skeleton from these parameters. Since I want to use many different models I would prefer to not create the skeletons by hand but in code.

I have been trying for over a week now to create a valid bone structure from these values that reflects the model, but nothing succeeded. For example, if I create a chain of bones using the positions of the joints I get a very long skeleton which in no way matches the positions I used.

let boneParent;
let bonepos = [];
let bones = [];
model.traverse(child => {
    switch(child.type) {
        case "joint":
            let p = new Vector3();
            child.getWorldPosition(p);
            bonepos.push(p);

            let bone = new Bone();
            boneParent && boneParent.add(p);
            bone.worldToLocal(p.clone());
            bone.position.copy(p);
            bone.rotation.copy(child.rotation);
            bone.scale.copy(child.scale);

            boneParent = bone;
            bones.push(bone);
            break;
    }
});
showPoints(scene, bonepos, 0xff0000);

const skeletonHelper = new SkeletonHelper(bones[0]);
skeletonHelper.visible = true;
scene.add(skeletonHelper);

The code above results in the screenshot below. The red markers are the positions I get from the robot joints, the line snaking into the distance is the skeleton as visualized by the SkeletonHelper.

Robot with very bad bones

So my question is this: it seems like I don't understand well enough how bones are handled in Three.js. How can I create a skeleton that reflects my existing model from its joint locations and orientations?

Thanks in advance!

For future visitors: Rocksi is open source!

2

There are 2 answers

2
Managarm On BEST ANSWER

After some fiddling around I found a solution:

let root = new Bone();
let parent = root;
let pos = new Vector3();

for (let joint of robot.arm.movable) {
    let link = robot.getLinkForJoint(joint);
    link.getWorldPosition(pos);
    
    let bone = new Bone();
    parent.add(bone);
    parent.lookAt(pos);
    parent.updateMatrixWorld();  // crucial for worldToLocal!
    bone.position.copy(bone.worldToLocal(pos));
        
    parent = bone;
}

The important part is to call updateMatrixWOrld() after lookAt() so that bone.worldToLocal() works correctly. Also lookAt() saves a lot of matrix hassles :)

3
Mugen87 On

child.getWorldPosition(p);

I'm afraid it's incorrect to apply the position in world space to Bone.position which represents the position in local space.

boneParent = bone;

This line looks problematic, too. A bone can have multiple child elements. It seems to me that this use case is not considered of your code.