How can an AnimationAction stop at the last frame without looping in three.js?

3.1k Views Asked by At

I'd like to stop an AnimationAction at the last frame that I've created with morph targets.

https://threejs.org/docs/#api/en/animation/AnimationAction

I've tried animationAction.clampWhenFinished = true; but that doesn't seem to work.

I've looked at older stackoverflow questions and searched through forums but the solutions didn't work.

var cubeTarget1 = new THREE.BoxGeometry(20, 10, 10);
var cubeTarget2 = new THREE.BoxGeometry(20, 10, 50);
var cubeTarget3 = new THREE.BoxGeometry(60, 10, 10);

cubeGeometry.morphTargets[0] = {name: 't1', vertices: cubeTarget1.vertices};
cubeGeometry.morphTargets[1] = {name: 't2', vertices: cubeTarget2.vertices};
cubeGeometry.morphTargets[2] = {name: 't3', vertices: cubeTarget3.vertices};

Is there a way I can do something like: (this doesn't work, it loops back to the first morphTarget)

var clip1 = THREE.AnimationClip.CreateFromMorphTargetSequence('run', [cubeGeometry.morphTargets[0],cubeGeometry.morphTargets[1]], 30);
var action1 = mixer.clipAction(clip1);
action1.play(); // starts at cubeTarget1 ends at cubeTarget2 (animating between them, without a loop)

// and at a later point I'd like to do

var clip2 = THREE.AnimationClip.CreateFromMorphTargetSequence('run', [cubeGeometry.morphTargets[1],cubeGeometry.morphTargets[2]], 30);
var action2 = mixer.clipAction(clip2);
action2.play(); // starts at cubeTarget2 ends at cubeTarget3 (animating between them, without a loop)

Here's my fiddle: https://jsfiddle.net/foreyez/uy8abk6v/

3

There are 3 best solutions below

0
Shai UI On

I looked at the three.js code. And inside LoopOnce the section involving clampWhenFinished doesn't get hit at all.

For now I'll do it in a very crude way until I find a better solution:

action.setDuration(5).play(); 
setTimeout(function()
{
    action.paused = true;
},2500); // half of the duration

Another way I've been doing is to use morphTargetInfluences and just increment it on an animation loop:

function animate() {
  if (cube.morphTargetInfluences[0] < 1)
    cube.morphTargetInfluences[0] += 0.01;

  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Use Tween.js if you need more functionality.

0
styl0r On

It took me a while to get this working, a lot of the online examples seem to be outdated and/or non-working. Try:

var clips = THREE.AnimationClip.CreateClipsFromMorphTargetSequences(geometry.morphAttributes.position, 60, true);
mixer = new THREE.AnimationMixer(points);
var action = mixer.clipAction(clips[0]).setDuration(10);
action.clampWhenFinished = true;
action.setLoop(THREE.LoopOnce);
action.play();

Note the "noLoop" parameter of CreateClipsFromMorphTargetSequences(name: String, morphTargetSequence: Array, fps: Number, noLoop: Boolean) needs to be "true" along with clampWhenFinished = true and setLoop(THREE.LoopOnce).

Full example here https://jsfiddle.net/jm4ersoq/

0
Shane Brumback On

This is my approach that I used with an enemy bot gltf model on my three.js prototype first person shooter. The robot has a single track animation with many frames. I had to split the frames up into sub clips with the following code then applied clampwhenfinished.

var EnemyHeavyBotFallBackClip = THREE.AnimationUtils.subclip(gltf.animations[0], “Take_001”, 1300, 1355);
actionEnemyHeavyBotFallBackMixer = mixer.clipAction(EnemyHeavyBotFallBackClip);
actionEnemyHeavyBotFallBackMixer.clampWhenFinished = true;
actionEnemyHeavyBotFallBackMixer.setLoop(THREE.LoopOnce);
actionEnemyHeavyBotFallBackMixer.play();

https://www.shanebrumback.com/super-soldier-battle-intro.html

Disclaimer: This is my website.