Reputation: 15424
Regarding this Three.js example https://threejs.org/examples/#webgl_animation_keyframes I was able to load my own model and play animation in a loop. However I want to make a "slider" that will allow user to control animation frame by frame. How I can achieve this with AnimationMixer? Let's say that animation clip has duration 4s. I would like to control in realtime current animation time from 0s to 4s.
Upvotes: 6
Views: 6445
Reputation: 1346
Alexander's answer is correct; to achieve what is described with a slider input control (i.e. like scrubbing a video) then the AnimationMixer.update
method is not appropriate.
Fortunately, there now exists a AnimationMixer.setTime
method to accomplish this.
It is implemented essentially as Alexander described, which you can see here: https://github.com/mrdoob/three.js/blob/r130/src/animation/AnimationMixer.js#L649-L661
Scrub/slide to an exact time in seconds:
mixer.setTime( exactTimeInSeconds )
Move forward in time by delta
amount of seconds:
Using the Clock.getDelta
method to get seconds passed since last call:
const delta = clock.getDelta(); // Optional: In this case we get delta from Three.js' own clock instead of a slider
mixer.update( delta );
All Together
To tie this all together, consider an example scenario where we want to get to the 3 second mark:
mixer.setTime(2); // time = 2 second mark
mixer.update(1); // time = 2 + 1 = 3 second mark
Upvotes: 1
Reputation: 28462
The answer is right there in the code of the example you linked:
mixer = new THREE.AnimationMixer( model );
mixer.clipAction( gltf.animations[ 0 ] ).play();
// ...
function animate() {
var delta = clock.getDelta();
mixer.update( delta );
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
mixer.update( delta );
is what updates the animation by 0.0166 seconds on every frame (at 60FPS). If you want to jump to a specific moment, simply assign the .time property to whatever second you need.
See here for more documentation on AnimationMixer.
Upvotes: 3
Reputation: 437
The other answer is incomplete. It would never work with a slider input as described in the original post. The problem is that the Animation Mixer update function changes the time relative to the current time and is subsequently unsuitable for a slider input as described.
Instead you want to make a function like this:
function seekAnimationTime(animMixer, timeInSeconds){
animMixer.time=0;
for(var i=0;i<animMixer._actions.length;i++){
animMixer._actions[i].time=0;
}
animMixer.update(timeInSeconds)
}
With this you could then run this in your slider OnChange event listener:
seekAnimationTime(yourAnimationMixer, event.value);
Or to set the animation time to 2s:
seekAnimationTime(yourAnimationMixer,2);
This solution will work regardless of what the previous animationMixer time was.
Upvotes: 4
Reputation: 3150
I've created some code that allows stepping the animation by percentage from 0% to 100%. You can find the JavaScript version below, or a TypeScript version here.
To my knowledge, three.js doesn't have per-frame stepping, but rather general interpolation info (though I could be wrong about that). IMO percentage-based seeking is more useful anyway as it allows consistent stepping regardless of mesh frame count.
Example Blender animation: | Controlling frame position by percentage with an external device: |
---|---|
/**
* Class that offers a means of controlling an animation by percentage.
*/
class AnimationSlider {
constructor(mesh) {
this._fakeDelta = 0;
this._mesh = mesh;
this._mixer = new THREE.AnimationMixer(mesh.scene);
this._clips = mesh.animations;
this._percentage = 0;
this.anims = [];
for (let i = 0, len = this._clips.length; i < len; i++) {
const clip = this._clips[i];
const action = this._mixer.clipAction(clip);
action.play();
action.paused = true;
//
this.anims.push({ clip, action });
}
}
/**
* Sets the animation to an exact point.
* @param percentage - A number from 0 to 1, where 0 is 0% and 1 is 100%.
*/
seek(percentage) {
if (this._percentage === percentage) {
return;
}
for (let i = 0, len = this.anims.length; i < len; i++) {
const { clip, action } = this.anims[i];
action.time = percentage * clip.duration;
this._mixer.update(++this._fakeDelta);
}
this._percentage = percentage;
}
}
// Create a slider instance:
const slider = new AnimationSlider(gltfMesh);
// Set the animation position to 40%:
slider.seek(0.4);
I believe that the code is self-explanatory, but if not, let me know and I'll riddle it with comments.
Upvotes: 1