Multiple GLTF files with different animations on one GLTF file model?

Hello, I’m new here. I’m currently working on my portfolio website and I’m having a little trouble getting multiple GLTF animations from a different file and playing it onto my main GLTF model.

I’ve followed a couple examples:

But my goal is a little different and I’m having trouble getting there. Currently, what I have is 2 GLTF files, one with Walk animation, the other Idle Animation. I’ve tried making separate mixers for each GLTF loaded and combining them, but it hasn’t worked. I’ve also tried creating a sharedSkinnedMesh, sharedSkeleton, and sharedParentBone, but that didn’t work either. I’ve tried getting the gltf.scene of each after it’s loaded by implementing an assetCount vs assetTotal check.

Here is my current code for loading gltf files and grabbing their anims (it’s a bit messy currently as I’ve tried a bunch of different things), I’d appreciate any tips or help on the matter:

function spawnGeos(){
    
    // CHARACTER GEOS
    assetList.forEach(function(each){
        gltfLoader.load(
            each,
            (gltf) =>
            {
                geo = gltf.scene
                animationsWalk= gltf.animations
               
        
                // skeleton = new THREE.SkeletonHelper(geo)
                // skeleton.visible = false
                // scene.add( skeleton )
            
                geo.traverse((obj) => {
                    if(obj.isMesh){
                        const oldMat = obj.material
                        obj.material = new THREE.MeshStandardMaterial({
                            color:oldMat.color,
                            map:oldMat.map,
                            bumpMap:oldMat.bumpMap,
                            displacementMap:oldMat.displacementMap,
                            alphaMap:oldMat.alphaMap,
                            normalMap:oldMat.normalMap,
                            aoMap:oldMat.aoMap
                        })
                        obj.castShadow = true
                        obj.receiveShadow = true
                        //console.log(obj)
                    }
                })
                for(let i=0; i < 6; i++){
                    
                    geo.children[0].children[i].material.side = THREE.DoubleSide
                }
                
                //setupWalkAnimations()
            },
            () =>
            {
                console.log('progress')
            },
            () =>
            {
                console.log('error')
            });
    })
    

    gltfLoader.load(
        idleModel,
        (gltf) =>
        {
            idleAnim=gltf.scene
            idleAnim.visible = false
            let x=4
            idleAnim.scale.set(x,x,x)


            animationsIdle = gltf.animations
            //setupIdleAnimations()
            //checkProgress()

        },
        () =>
        {
            console.log('progress')
        },
        () =>
        {
            console.log('error')
        });
    
        checkProgress()
}

function checkProgress(){
    assetCount+=1
    console.log('assetCount:' + assetCount)
    console.log('assetTotal:' + assetTotal)
    
        
    
    if(assetCount == assetTotal-1){
        sharedAnimSetup()
        setupWalkAnimations()
        }
    if(assetCount == assetTotal){   
        setupIdleAnimations()
        displayData()}
    renderer.setAnimationLoop(animate)
}



function sharedAnimSetup(){
    
    const identity = new THREE.Matrix4();
    let shareSkinnedMesh
    let sharedSkeleton
    let sharedParentBone
    shareSkinnedMesh = sharedModel.getObjectByName( 'IbernianWarrior_body' );
    sharedSkeleton = shareSkinnedMesh.skeleton;
    sharedParentBone = sharedModel.getObjectByName('JNT_pelvis')
    scene.add(sharedParentBone)
    let model1 = shareSkinnedMesh.clone();
    model1.bindMode = THREE.DetachedBindMode;

    model1.position.set(0,-.1,0)

    skeleton = new THREE.SkeletonHelper(sharedParentBone)
    skeleton.visible = false
    scene.add( skeleton )

    sharedParentBone.scale.set(x,x,x)

    model1.bind(sharedSkeleton, identity)
    //console.log(geo)
    // if(assetCount == assetTotal-1){
    //     let sharedModel = SkeletonUtils.clone(geo)
    //     // const shareSkinnedMesh = []
    //     // geo.traverse((obj) => {
    //     //     if(obj.isMesh){
    //     //         shareSkinnedMesh.push(sharedModel.getObjectByName(obj.name))
    //     //     }
    //     // });
    //     shareSkinnedMesh = sharedModel.getObjectByName( 'IbernianWarrior_body' );
    //     sharedSkeleton = shareSkinnedMesh.skeleton;
    //     sharedParentBone = sharedModel.getObjectByName('JNT_pelvis')
    //     scene.add(sharedParentBone)
    
    //     const model1 = shareSkinnedMesh.clone();
    //     model1.bindMode = THREE.DetachedBindMode;

    //     let x=6
    //     //model1.scale.set(x,x,x)
    //     model1.position.set(0,-.1,0)
    
    //     skeleton = new THREE.SkeletonHelper(sharedParentBone)
    //     skeleton.visible = false
    //     scene.add( skeleton )
    
    //     sharedParentBone.scale.set(x,x,x)


    //     model1.bind(sharedSkeleton, identity)

    //     let mixer = new THREE.AnimationMixer(sharedParentBone)
    //     if(assetCount >= assetTotal-1){
    //         tPoseClip = THREE.AnimationUtils.subclip(animations[0],'All Animations', 1,49,60)
    //         tPoseAction = mixer.clipAction(tPoseClip);
            
    //         walkClip = THREE.AnimationUtils.subclip(animations[0],'All Animations',50,149,60)
    //         walkAction = mixer.clipAction(walkClip);
    //     }
    //     activateAllActions()
    //     scene.add(sharedParentBone,model1)
    //     mixers.push(mixer)
    // }
    // if(assetCount >= assetTotal){
    //     // Character Animations
    //     const walkClone = shareSkinnedMesh.clone();
    //     const idleClone = shareSkinnedMesh.clone();


    //     walkClone.bindMode = THREE.DetachedBindMode;
    //     idleClone.bindMode = THREE.DetachedBindMode;

    //     //sharedParentBone.position.set(0,-.1,0)



    //     walkClone.bind(sharedSkeleton, identity)
    //     idleClone.bind(sharedSkeleton, identity)


    //     let mixer = new THREE.AnimationMixer(sharedParentBone)
    //     if(assetCount >= assetTotal){
    //         idleClip = THREE.AnimationUtils.subclip(animations[0],'All Animations',1,149,60)
    //         idleAction = mixer.clipAction(idleClip);
    //     }
    //     activateAllActions()
    //     scene.add(walkClone,idleClone)
    //     mixers.push(mixer)
    // }
}

function setupWalkAnimations(){


    // Character Animations
    //const walkClone = SkeletonUtils.clone(geo);
   
    // WALK
    
    const mixerWalk = new THREE.AnimationMixer(model1)

    tPoseClip = THREE.AnimationUtils.subclip(animationsWalk[0],'All Animations', 1,49,60)
    tPoseAction = mixerWalk.clipAction(tPoseClip);
    
    walkClip = THREE.AnimationUtils.subclip(animationsWalk[0],'All Animations',50,149,60)
    walkAction = mixerWalk.clipAction(walkClip);

    actions.push(tPoseAction,walkAction)
    activateAllActions()
    scene.add(walkClone)
    
    mixers.push(mixerWalk)
}

function setupIdleAnimations(){
    // Character Animations
    const idleClone = SkeletonUtils.clone(idleAnim);

    // IDLE
    
    const mixerIdle = new THREE.AnimationMixer(idleClone)

    idleClip = THREE.AnimationUtils.subclip(animationsIdle[0],'All Animations',1,59,60)
    idleAction = mixerIdle.clipAction(idleClip);

    animSkel = new THREE.SkeletonHelper(idleAnim)
    animSkel.visible = false
    scene.add( animSkel )

    actions.push(idleAction)
    activateAllActions()
    scene.add(idleClone)
    mixers.push(mixerIdle)
    
    // window.addEventListener('keydown', (sdown) => {
        
    //     if(sdown.key == 's'){}
    // });

    // window.addEventListener('keydown', (wdown) => {
        
    //     if(wdown.key == 'w'){}
    // });
}

I’ve done this before in C#, C++, Unity, and Unreal Engine, but it’s a little different with Three Js.
Again, any help is appreciated! Thank you in advance.

Hello @mjv92 ,

Have you found a solution for this ?
I have a similar issue : multiple gltf files containing one animation for the same model.
I’d like to combine them to be able to transition smoothly from one animation to another.

Currently I’m doing this with multiple mixers, one for each file loaded, using fadeIn/fadeOut but the rendering is not great at all.

you can use one mixer for everything, and play multiple animations on a skinnedmesh, but you have to pass the skinnedMesh into the mixer.clipAction() call as the “optionalRoot” parameter.

You can then set the .weight of the resulting actions to blend between them.

so you do something like:

let meshA = someSkinnedMesh;
let meshB = SkeletonUtils.clone(meshA); //Use skelutils to clone skinnedmesh correctly...

scene.add(meshA);
scene.add(meshB);

let walkA = mixer.clipAction(walkanimation, meshA).play();
let idleA = mixer.clipAction(idleanimation, meshA).play();

walkA.weight = .5;
idleA.weight = .5; //50% blend between the 2 anims...



let walkB = mixer.clipAction(walkanimation, meshB).play();
let idleB = mixer.clipAction(idleanimation, meshB).play();

walkB.weight = .5;
idleB.weight = .5; //50% blend between the 2 anims...

or use the

methods to blend between them over time via the mixer.

1 Like

The parameter optionalRoot for clipAction() did the trick.
Thank you so much, you saved my day !

1 Like