i am using a dog model with root motion animation. When i am playing the animation for few steps it works fine, but when animation loops back the model moves again back to first keyframe position.
How to fix this ? I tried with clampWhenFinished = true but still facing same issue.
I started writing an answer but then asked gemini. It came up with the same answer, but much more complete. My answer was basically the " 2. The Three.js Code Fix (Advanced)" answer… so I’m just pasting the whole answer here verbatim:
This is a very common issue when trying to use root motion with looping animations in Three.js, especially when the animation is designed to cover a distance, like a few steps.
The clampWhenFinished = true setting only prevents the animation from resetting after it’s completely done playing. However, in a looping animation, the issue is that the animation data itself defines a starting position (keyframe 0) and an ending position (last keyframe). When the loop restarts, the animation system interpolates from the last keyframe’s position back to the first keyframe’s position, causing the sudden jump back.
Here’s a breakdown of the problem and the best solutions:
Understanding the Problem: The Root Bone
The core issue lies in how your root bone (or the top-level bone of the model) is animated in the source file:
- Start of Animation (Keyframe 0): The root bone is at position (0, 0, 0) (or some initial offset).
- End of Animation (Last Keyframe): The root bone has been translated forward (e.g., to position (X, 0, 0)) to simulate the distance covered by the steps (this is the root motion).
- Loop: The animation system tries to smoothly transition the root bone from position (X, 0, 0) back to (0, 0, 0) for the next loop, causing the model to snap back to its original starting point.
Solutions for Looping Root Motion
You have two main approaches to fix this, depending on how much control you want in Three.js versus your 3D modeling/export tool (Blender, Maya, etc.).
1. The Modeling/Export Fix (Recommended)
The ideal fix is to create a “proper” root motion cycle in your 3D application before export:
- Remove Root Bone Translation: The animation data itself should NOT contain any translation for the root bone. The root bone should remain at its starting position (0, 0, 0) throughout the entire animation.
- The Model Stays in Place: The character’s feet should still move and plant on the “ground,” but the entire model’s animation should happen in place, like a treadmill.
- Export/Use as In-Place Animation: Export this animation. When you play it in Three.js, the dog will appear to run in place.
With the animation running in place, you then apply the forward movement manually:
`JavaScript// Assume ‘mixer’ is your THREE.AnimationMixer and ‘walkAction’ is your THREE.AnimationAction
walkAction.setLoop(THREE.LoopRepeat).play();
const speed = 0.05; // Adjust this value
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
// Manually translate the model forward in the scene
// The model is the parent Object3D that holds the dog mesh
dogModel.position.x += speed; // Or dogModel.translateZ(speed);
renderer.render(scene, camera);
}`
This way, the model moves forward in the scene, and the animation loops seamlessly because the animation data itself is perfectly cyclical (it begins and ends at the same root position).
2. The Three.js Code Fix (Advanced)
If you cannot change the source animation, you can try to manually compensate for the root bone’s movement after the animation system applies it, which is complex:
- Detect the Loop: You need to listen for the
loopevent on theAnimationMixer. - Calculate Offset: When the animation is loaded, you need to determine the total translation the root bone performs over one full cycle (e.g., (X, 0, 0)).
- Apply Compensation: When the loop event fires, you manually apply a reverse translation to the entire character model to keep its global position consistent.
`JavaScript// — WARNING: This is a complex workaround —
// 1. Get the bone translation offset from the source data
// (You need to inspect the keyframe track for the root bone to find the total distance X)
const animationDistanceX = 1.25; // Example: 1.25 units of movement per loop
mixer.addEventListener(‘loop’, (event) => {
// Check if it’s the specific action
if (event.action === walkAction) {
// Manually move the entire model forward by the distance it jumped back
// ‘dogModel’ is the parent Object3D for the entire character
dogModel.position.x += animationDistanceX;
}
});
walkAction.setLoop(THREE.LoopRepeat).play();`
Recommendation: The first method (In-Place Animation + Manual Translation) is the standard, cleaner, and most robust solution for implementing root motion in Three.js and similar game engines.
(I disagree with the recommendation, since it requires strong knowlege of animation in blender, and it’s easy to lose some of the nuance of the animated performance in the process… So i myself prefer the threejs solution part of the answer).. That all said.. doing it in threejs can also get more complex when you start needing to blend different animations with different timings. So in that case, maybe Gemini’s advice is sound.
It’s a complicated issue with no easy answer imo.