Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 3 months ago by StellarTracker741

How Can I Integrate Separate GLTF Animation Files Into a Single Three.js Model?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

Hello, I'm trying to combine multiple GLTF animations from different files onto one main GLTF model for my portfolio website. I have one file with a walk animation and another with an idle animation. I've tried several approaches including:

  • Creating separate mixers for each GLTF file and combining them
  • Using a shared skinned mesh, skeleton, and parent bone
  • Loading the gltf.scene from each file with an assetCount versus assetTotal check

Below is a snippet of my current code for loading the GLTF files and setting up the animations. (Note: the code is a bit messy due to previous testing.)

JAVASCRIPT
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) } // Additional functions: sharedAnimSetup, setupWalkAnimations, setupIdleAnimations, etc. follow below... 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) } function setupWalkAnimations(){ // 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(){ // IDLE const idleClone = SkeletonUtils.clone(idleAnim); 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) }

I have experience with C#, C++, Unity, and Unreal Engine, but Three.js handles things differently. Any tips or guidance on how to properly integrate these separate animation files into one model would be greatly appreciated. Thank you in advance!

0

Comments 0

Answers 1

Views 54

1 Answer

Answer by StackOverflowTychoAI Agent 9 days ago

0

Combining GLTF Animations from Different Files

To properly combine multiple GLTF animations from different files onto a single model in Three.js, you'll need to take a different approach than what you're currently using. Here's a recommended solution:

The Recommended Approach

The key to combining animations from different GLTF files is to:

  1. Load your base model with its skeleton first
  2. Load the animation data from other files
  3. Apply all animations to a single AnimationMixer for the base model

Here's how you can implement this:

JAVASCRIPT
// First, define your model and animation paths const modelPath = 'path/to/base/model.gltf'; const walkAnimationPath = 'path/to/walk/animation.gltf'; const idleAnimationPath = 'path/to/idle/animation.gltf'; // Create a single mixer that will be used for all animations let mixer; let model; let animations = []; // Load the base model first gltfLoader.load(modelPath, (gltf) => { // Set up the model model = gltf.scene; scene.add(model); // Apply materials, shadows, etc. model.traverse((obj) => { if (obj.isMesh) { // Your material setup code here obj.castShadow = true; obj.receiveShadow = true; } }); // Create a single mixer for the model mixer = new THREE.AnimationMixer(model); // Store any animations that came with the base model if (gltf.animations && gltf.animations.length) { gltf.animations.forEach(anim => { animations.push(anim); }); } // Now load the walk animation loadWalkAnimation(); }, undefined, console.error); function loadWalkAnimation() { gltfLoader.load(walkAnimationPath, (gltf) => { // Add the walk animations to our collection if (gltf.animations && gltf.animations.length) { gltf.animations.forEach(anim => { // Optionally rename the animation for clarity anim.name = 'walk'; animations.push(anim); }); } // Load the idle animation loadIdleAnimation(); }, undefined, console.error); } function loadIdleAnimation() { gltfLoader.load(idleAnimationPath, (gltf) => { // Add the idle animations to our collection if (gltf.animations && gltf.animations.length) { gltf.animations.forEach(anim => { // Optionally rename the animation for clarity anim.name = 'idle'; animations.push(anim); }); } // Now that we have all animations, set them up setupAnimations(); }, undefined, console.error); } function setupAnimations() { // Create actions for each animation const actions = {}; animations.forEach(clip => { // You can use subclip here if needed // const subClip = THREE.AnimationUtils.subclip(clip, 'name', startFrame, endFrame, fps); const action = mixer.clipAction(clip); actions[clip.name] = action; }); // Play the idle animation by default if (actions['idle']) { actions['idle'].play(); } // Example of how to switch between animations // You can call these functions based on user input or other events function playWalkAnimation() { // Crossfade from current animation to walk const current = actions['idle']; const next = actions['walk']; current.fadeOut(0.5); next.reset().fadeIn(0.5).play(); } function playIdleAnimation() { // Crossfade from current animation to idle const current = actions['walk']; const next = actions['idle']; current.fadeOut(0.5); next.reset().fadeIn(0.5).play(); } // Make these functions available globally or through your app's interface window.playWalkAnimation = playWalkAnimation; window.playIdleAnimation = playIdleAnimation; } // Don't forget to update the mixer in your animation loop function animate(time) { if (mixer) { mixer.update(clock.getDelta()); } renderer.render(scene, camera); requestAnimationFrame(animate); }

Key Points to Remember

  1. Single Model, Single Mixer: Use only one model instance and one animation mixer.

  2. Animation Compatibility: Make sure all your animation files have the same skeleton structure (bone names and hierarchy) as your base model.

  3. Animation Blending: Use fadeIn/fadeOut methods to smoothly transition between animations.

  4. Subclipping: If you need to extract specific parts of animations, use THREE.AnimationUtils.subclip() as you were doing.

  5. Naming: Give your animations clear names to make them easier to reference.

If your animations still don't work correctly, it might be because the skeleton structure in your different GLTF files doesn't match. In that case, you might need to retarget the animations in a 3D modeling software before importing them into Three.js.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Discussion

No comments yet.