Sharing a skeleton between different skinned meshes

I am working on an application that has several body shapes of different sizes (tall, short, slim, full) and some clothing to attach based on user input. Both the bodies and clothing models (all GLTF) are rigged in the same way.

When a body is selected, I save the skeleton and then when a piece of clothing is selected, I overwrite its skeleton with the saved one using code something like this:

function setSkeleton(model, skeleton) {
    model.traverse(function (object) {
        if (object.isSkinnedMesh) {
            object.skeleton.bones.forEach(function (bone, index) {
                bone.position.copy(skeleton.bones[index].position);
                bone.scale.copy(skeleton.bones[index].scale);
                bone.rotation.copy(skeleton.bones[index].rotation);
            });

            object.skeleton.update();
        }
    });
}

It mostly works okay but sometimes, the clothing doesn’t fit well over the body and I think that’s because I am missing something when I copy over the skeleton.

Can anyone point me at a working example that implements a similar approach?

I don’t think that clothing will adapt/morph to a different shaped skeleton unless the new skeletons only difference is that the bones scaling brings it into the new body shape… then its possible for the cloths to rescale since the are rigged to the same bones…

Understood and I believe that’s the case. The content creators are using a 3rd party plugin to Blender but that’s leading to some other issues so I was trying to figure it out in code.

It’s very close - looks like it might be a Y/Z up problem given the monster that appears after I add the code but I’ve tried every possibility.

Ahh yes I know that freaky monster well.

I wish I could share some sample code and assets for the moment at least, it’s all internal…

I have a “base” GLTF model containing skinned meshes with the same skeleton and then separately, an XML file with a list of bone position and scale deltas.

For each bone in the skeleton, I grab the bone position and scale, then add in each of the X,Y,Z deltas for both, and then set them back in the skeleton.

const fpx = px + px_d;  
etc.
etc.
bone.position.copy(new THREE.Vector3(fpx, fpy, fpz));

const fsx = sx + sx_d;  
etc.
etc.
bone.scale.copy(new THREE.Vector3(fsx, fsy, fsz));

and then after I have updated each bone, I make these calls:

bone.matrixWorldNeedsUpdate = true;
bone.matrixAutoUpdate = true;
bone.updateMatrixWorld(true);

but I feel like my 3D/vector math is sorely lacking and this is plain wrong or incomplete… For example, when I use this approach to move the hands, both hands move in the same direction even though the delta in the XML file has one positive and one negative.

Any knowledgable people in this field know what I am likely doing wrong?

When exporting GLTF, there is a transformation from Z up to Y up coordinate space.
Once you export your model to GLTF, any external data/deltas you had beforehand will no longer apply to the exported GLTF unless you convert that data to Y up also. This means, swapping the Y and Z values on all vectors, and possibly negating X. For rotations/quaternions… it probably involves applying a 90 degree rotation to rotate the Z onto the Y axis. Yuck.

Alternatively You can try disabling the up vector conversion behavior in the Blender exporter in the GLTF export settings-> Transform tab-> uncheck +Y up. This might keep your models matching the space of your data.

Your models will come in lying on their back, but thats easy to correct by putting the whole thing under a new node or just applying a rotation to the top most non-animated node.

r.e. your code…

you don’t need to do:


bone.position.copy(new THREE.Vector3(fpx, fpy, fpz));
bone.scale.copy(new THREE.Vector3(fsx, fsy, fsz));

you can just do:


bone.position.set(fpx, fpy, fpz);
bone.scale.set(fsx, fsy, fsz);

You don’t need these:


bone.matrixWorldNeedsUpdate = true;
bone.matrixAutoUpdate = true;

Just bone.updateMatrixWorld(true);
should cover it, (and might be overkill… you can probably just do bone.updateMatrix() or even nothing at all, assuming its matrixAutoUpdate = true (the default )… it should recompute the whole hierarachies matrix+matrixWorld at the next render.

Hope this helps! :smiley:

Loads to digest and experiment with there. Thank you so much. I’ll report back if I have some success.