You can load a single THREE.AnimationClip and use it in two different models.
In this example, two different model files are loaded along with three different animation clips.
A custom animation clip is also created which is first cloned from one of the other animation clips and all keytracks are removed except for a select few. See the clonedRightArm buttons.
The same animation clips can be applied to either models. This is possible since the key tracks in the animation clips refer to named bones that already exist within the loaded models.
The bones contained in each models skeletons share the same hierarchy and naming.
import*asTHREEfrom'three'import{OrbitControls}from'three/examples/jsm/controls/OrbitControls'import{GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader'importStatsfrom'three/examples/jsm/libs/stats.module'import{GUI}from'dat.gui'constscene=newTHREE.Scene()scene.add(newTHREE.AxesHelper(5))constlight1=newTHREE.SpotLight(0xffffff,100)light1.position.set(2.5,5,5)light1.angle=Math.PI/4light1.penumbra=0.5light1.castShadow=truelight1.shadow.mapSize.width=1024light1.shadow.mapSize.height=1024light1.shadow.camera.near=0.5light1.shadow.camera.far=20scene.add(light1)constlight2=newTHREE.SpotLight(0xffffff,100)light2.position.set(-2.5,5,5)light2.angle=Math.PI/4light2.penumbra=0.5light2.castShadow=truelight2.shadow.mapSize.width=1024light2.shadow.mapSize.height=1024light2.shadow.camera.near=0.5light2.shadow.camera.far=20scene.add(light2)constcamera=newTHREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)camera.position.y=1.5camera.position.z=2.5constrenderer=newTHREE.WebGLRenderer()renderer.shadowMap.enabled=truerenderer.setSize(window.innerWidth,window.innerHeight)document.body.appendChild(renderer.domElement)constcontrols=newOrbitControls(camera,renderer.domElement)controls.enableDamping=truecontrols.target.y=1constanimationClips:{[key:string]:THREE.AnimationClip}={}letxbotMixer:THREE.AnimationMixerletybotMixer:THREE.AnimationMixerletxbotLastAction:THREE.AnimationClipletybotLastAction:THREE.AnimationClipconsttotalBots=2letbotsLoaded=0letbotsReady=falseconstgltfLoader=newGLTFLoader()gltfLoader.load('models/xbot.glb',(gltf)=>{xbotMixer=newTHREE.AnimationMixer(gltf.scene)animationClips['xbotDefault']=gltf.animations[0]xbotLastAction=animationClips['xbotDefault']xbotFolder.add(xbotButtons,'default')gltf.scene.traverse(function(child){if((childasTHREE.Mesh).isMesh){constm=childasTHREE.Meshm.castShadow=true}})gltf.scene.position.x=-1consthelper=newTHREE.SkeletonHelper(gltf.scene)scene.add(helper)scene.add(gltf.scene)botsLoaded++loadAnimations()},(xhr)=>{console.log((xhr.loaded/xhr.total)*100+'% loaded')},(error)=>{console.log(error)})gltfLoader.load('models/ybot.glb',function(gltf){ybotMixer=newTHREE.AnimationMixer(gltf.scene)animationClips['ybotDefault']=gltf.animations[0]ybotLastAction=animationClips['ybotDefault']ybotFolder.add(ybotButtons,'default')gltf.scene.traverse(function(child){console.log(child.name+' '+child.type)if((childasTHREE.Mesh).isMesh){constm=childasTHREE.Meshm.castShadow=true}})gltf.scene.position.x=1consthelper=newTHREE.SkeletonHelper(gltf.scene)scene.add(helper)scene.add(gltf.scene)botsLoaded++loadAnimations()},(xhr)=>{console.log((xhr.loaded/xhr.total)*100+'% loaded')},(error)=>{console.log(error)})functionloadAnimations(){if(botsLoaded===totalBots){//add an animation from another filegltfLoader.load('models/actionClip@samba.glb',(gltf)=>{console.log('loaded samba')animationClips['samba']=gltf.animations[0]xbotFolder.add(xbotButtons,'samba')ybotFolder.add(ybotButtons,'samba')//add an animation from another filegltfLoader.load('models/actionClip@bellydance.glb',(gltf)=>{console.log('loaded bellyDance')animationClips['bellyDance']=gltf.animations[0]xbotFolder.add(xbotButtons,'bellyDance')ybotFolder.add(ybotButtons,'bellyDance')//add an animation from another filegltfLoader.load('models/actionClip@goofyrunning.glb',(gltf)=>{console.log('loaded goofyRunning');(gltfasany).animations[0].tracks.shift()//delete the specific track that moves the object forward while runninganimationClips['goofyRunning']=gltf.animations[0]xbotFolder.add(xbotButtons,'goofyRunning')ybotFolder.add(ybotButtons,'goofyRunning')//clone goofyrunning and create an animation clip using just one of the armsanimationClips['clonedRightArm']=animationClips['goofyRunning'].clone()leti=animationClips['clonedRightArm'].tracks.lengthwhile(i--){lettrackName=animationClips['clonedRightArm'].tracks[i].nameif(!(trackName.startsWith('mixamorigRightShoulder')||trackName.startsWith('mixamorigRightArm')||trackName.startsWith('mixamorigRightForeArm')||trackName.startsWith('mixamorigRightHand'))){animationClips['clonedRightArm'].tracks.splice(i,1)}}xbotFolder.add(xbotButtons,'clonedRightArm')ybotFolder.add(ybotButtons,'clonedRightArm')console.log(animationClips['clonedRightArm'])botsReady=true},(xhr)=>{console.log((xhr.loaded/xhr.total)*100+'% loaded')},(error)=>{console.log(error)})},(xhr)=>{console.log((xhr.loaded/xhr.total)*100+'% loaded')},(error)=>{console.log(error)})},(xhr)=>{console.log((xhr.loaded/xhr.total)*100+'% loaded')},(error)=>{console.log(error)})}}constphongMaterial=newTHREE.MeshPhongMaterial()constplaneGeometry=newTHREE.PlaneGeometry(25,25)constplaneMesh=newTHREE.Mesh(planeGeometry,phongMaterial)planeMesh.rotateX(-Math.PI/2)planeMesh.receiveShadow=truescene.add(planeMesh)window.addEventListener('resize',onWindowResize,false)functiononWindowResize(){camera.aspect=window.innerWidth/window.innerHeightcamera.updateProjectionMatrix()renderer.setSize(window.innerWidth,window.innerHeight)render()}constxbotButtons={default:function(){xbotMixer.clipAction(xbotLastAction).fadeOut(0.5)xbotMixer.clipAction(animationClips['xbotDefault']).reset().fadeIn(0.5).play()xbotLastAction=animationClips['xbotDefault']},samba:function(){xbotMixer.clipAction(xbotLastAction).fadeOut(0.5)xbotMixer.clipAction(animationClips['samba']).reset().fadeIn(0.5).play()xbotLastAction=animationClips['samba']},bellyDance:function(){xbotMixer.clipAction(xbotLastAction).fadeOut(0.5)xbotMixer.clipAction(animationClips['bellyDance']).reset().fadeIn(0.5).play()xbotLastAction=animationClips['bellyDance']},goofyRunning:function(){xbotMixer.clipAction(xbotLastAction).fadeOut(0.5)xbotMixer.clipAction(animationClips['goofyRunning']).reset().fadeIn(0.5).play()xbotLastAction=animationClips['goofyRunning']},clonedRightArm:function(){xbotMixer.clipAction(xbotLastAction).fadeOut(0.5)xbotMixer.clipAction(animationClips['clonedRightArm']).reset().fadeIn(0.5).play()xbotLastAction=animationClips['clonedRightArm']},}constybotButtons={default:function(){ybotMixer.clipAction(ybotLastAction).fadeOut(0.5)ybotMixer.clipAction(animationClips['ybotDefault']).reset().fadeIn(0.5).play()ybotLastAction=animationClips['ybotDefault']},samba:function(){ybotMixer.clipAction(ybotLastAction).fadeOut(0.5)ybotMixer.clipAction(animationClips['samba']).reset().fadeIn(0.5).play()ybotLastAction=animationClips['samba']},bellyDance:function(){ybotMixer.clipAction(ybotLastAction).fadeOut(0.5)ybotMixer.clipAction(animationClips['bellyDance']).reset().fadeIn(0.5).play()ybotLastAction=animationClips['bellyDance']},goofyRunning:function(){ybotMixer.clipAction(ybotLastAction).fadeOut(0.5)ybotMixer.clipAction(animationClips['goofyRunning']).reset().fadeIn(0.5).play()ybotLastAction=animationClips['goofyRunning']},clonedRightArm:function(){ybotMixer.clipAction(ybotLastAction).fadeOut(0.5)ybotMixer.clipAction(animationClips['clonedRightArm']).reset().fadeIn(0.5).play()ybotLastAction=animationClips['clonedRightArm']},}constgui=newGUI()constxbotFolder=gui.addFolder('xbot')xbotFolder.open()constybotFolder=gui.addFolder('ybot')ybotFolder.open()conststats=newStats()document.body.appendChild(stats.dom)constclock=newTHREE.Clock()letdelta=0functionanimate(){requestAnimationFrame(animate)controls.update()delta=clock.getDelta()if(botsReady){xbotMixer.update(delta)ybotMixer.update(delta)}render()stats.update()}functionrender(){renderer.render(scene,camera)}animate()