Load a GLTF rigged mesh and add it to a skeleton of an existing rigged mesh

I have an avatar body as a GLTF mesh, rigged to a skeleton which I load into my scene. I have a t-shirt as a separate GLTF mesh that has also been rigged to the same skeleton.

After I load the t-shirt mesh, what do I need to do in order to attach it to the same skeleton - I.E. so the avatar ‘wears’ the t-shirt?

I’ve been experimenting and the process seems to be load the avatar, find the skeleton, load the t-shirt and add it to the same skeleton but so far, I have been unable to make that work.

To elaborate on what I have tried so far - load the avatar GLTF model and traverse over it looking for the skeleton that is a child of the skinned mesh:

    const avatar = SkeletonUtils.clone(gltf.scene);
    avatar.traverse(function (object) {
        if (object.type == 'SkinnedMesh') {
            let bones = SkeletonUtils.getBones(object.skeleton);
            skeleton = new THREE.Skeleton(bones);
        }
    });

then, when I load the clothing, traverse the GLTF and when I find the skinned mesh it contains, call

    const clothing = SkeletonUtils.clone(gltf.scene);
    clothing.traverse(function (object) {
        if (object.type == 'SkinnedMesh') {
            object.bind(skeleton);
        }
    });

The code doesn’t appear to do have any effect but I’m not sure if this is a valid approach or my code is wrong.

1 Like

If your avatars are driven by AnimationClip then you can just include the t-shirt to the same AnimationMixer as AnimationObjectGroup as long as their rig is identical. Something like this:

animGroup = new AnimationObjectGroup(avatar, clothing)
mixer = new AnimationMixer(animGroup)
anim = mixer.clipAction(avatar.animation)

I see and I actually (blindly) tried animating the shirt to see what effect that had but there is no animation present in any of the clothing. I think what you posted is a little different from what I tried though so I’ll dig a bit more.

Thank you!

Yep - as before, when this stanza is executed:

let anim = mixer.clipAction(avatar.animation)
anim.play()

I get an error in the console:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'uuid')

which I think indicates there is no animation present in the clothing GLTF.

Is that expected and both need the same animation or is my code broken and the clothing should be able to inherit the animation from the clothing?

In my case, it works that a skinned mesh that has no animation but has the same rig works with the animation from the another skinned mesh. Although the target skinned mesh that has no animation was spun off from the .gltf that has the animation although they are in separate .gltfs.

Any chance the rig is not identical? Are their bones naming and structure are the same in both .gltfs?

Understood - I’m told both rigs are identical but I think I know how to confirm that by writing some code to inspect both skeletons.

I’ll have a look now - helpful to know it should work.

Thank you.

1 Like

I figured out what I was doing wrong with your code fragment and now it works although it still works after removing all the other binding/skeleton/skeletonutils code I added trying to debug it so the collection of theories I amassed were evidently all wrong.

I was foolishly trying to get the animation from gltf.scene.animation as per your code vs. glft.animations .

Main task now is to find out why it works with really just those 2-3 lines of code and everything else remove.

Many thanks - I marked your answer as the solution.

1 Like

No problem! Thanks for keeping me informed!

Excuse the follow up in the same post but it’s certainly related…

When I load the initial avatar, I create a THREE.AnimationObjectGroup, add the avatar to it, create a THREE.AnimationMixer and start it playing with animationMixer.clipAction(avatarGLTF.animations[0]).play(); - that works correctly.

Then, later when the user adds or removes an article of clothing, I add/remove it to/from the scene and also now call animationGroup.add(object) or animationGroup.remove(obj) respectively. After this, I would expect to have an animation group containing all the worn clothing.

This is the what my previous test was able to achieve based on your earlier answer by adding both the avatar and clothing explicitly in the animationGroup constructor and it worked perfectly.

As far as I can tell, adding or removing items from the animation group is not working as expected. I have tried a few things such as stopping and restarting the animation, without success

The debug stats in animationObjectGroup.stats.objects.inUse suggest that things are being added and removed correctly.

Any ideas what piece I am missing?

Not sure if I understood the problem correctly but the issue is when you add/remove clothing meshes dynamically to the AnimationObjectGroup instead of when you construct it?

If so, I can confirm it works for me when I just add another object to AnimationObjectGroup by calling .add(anotherSkinnedMesh). From the screen rec, the hat and boots are added to the group after couple seconds. My test code looks something like this:

animGroup = new AnimationObjectGroup(bodyGLTF.scene);

mixer = new AnimationMixer(animGroup);
anim = mixer.clipAction(body.animations[0]);
anim.play();

setTimeout(() => {
    animGroup.add(propsGLTF.scene);
}, 5000);

ezgif.com-gif-maker (6)

Yep, that’s it exactly except I also remove items - either if it’s replaced by another item of same type or just removed.

Really helpful knowing it should work so your assertion is immensely helpful.

Thank you.

No problem! Replacing it should be just combinations of .add() / remove(). I can confirmed this works for me:

setTimeout(() => {
    group.add(props);
}, 3000);

setTimeout(() => {
    props.visible = false;
    group.remove(props);
}, 6000);

setTimeout(() => {
    props.visible = true;
    group.add(props);
}, 9000);

ezgif.com-gif-maker (7)

Thank you for digging a bit deeper and I see what you mean.

I add the avatar to the group and then if I do not call stop() / start() on the animation either side of adding the clothing, the clothing mesh does not properly apply to the avatar body.

Calling stop/start is quite jarring - I’ve tried to record the time before I stop and then use setTime before I start playing again but something is not quite right.

Thank you.

Oh hmm, I did not need to call stop() and restart the animation clip when I add/remove the props to the group. There might be some other things that cause the issue. Are you available to share the sample .gltf perhaps a body with one clothing? It might be helpful to debug

I wish I could share but i’ve been told it’s not yet shareable sadly - I’m going to try to secure some different assets that behave in the same way - I realize how useful being able to share a problem in a fiddle/codepen is.

My best guess is that it’s something to do with the body model only animating the upper torso and not the lower part since the shirt works as expected and the pants do not.

The only solution I am poking at it to see if I can record the time, stop it, add to the group then restart the animation but starting it from the saved time… not doing the right thing currently but there might be something there.

One more thought - I assume from your code that everything is loaded and added to the scene and you are just setting the visible flag - I am loading things on demand, adding to the scene then adding to the animation group - maybe that is an important difference. Trying to reorg my code to more closely match that.

I rewrote the app to load everything, add to the scene and hide/show instead of load/add on demand. Works as expected now but I would still love to know why my earlier approach failed - best guess is content but might be a three.js bug. Our content team is going to give me some options so that might shed some light on it. Will post here if I find anything else.

1 Like