Skip to content

Instantly share code, notes, and snippets.

@rtpHarry
Last active October 4, 2023 06:20
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rtpHarry/2d41811d04825935039dfc075116d0ad to your computer and use it in GitHub Desktop.
Save rtpHarry/2d41811d04825935039dfc075116d0ad to your computer and use it in GitHub Desktop.
Three.js - play an AnimationAction in reverse. There are a bunch of threads saying this isn't possible but I found a way so I wanted to post it online in a place that people will hopefully stumble upon it.
// The class itself is based on the animation helper class in
// https://github.com/paulmg/ThreeJS-Webpack-ES6-Boilerplate
// but I have changed almost everything except for the class name and the update function.
import * as THREE from 'three';
export default class Animation {
constructor(scene, animations) {
this.scene = scene;
this.animations = animations;
this.mixer = new THREE.AnimationMixer(this.scene);
}
playClipByIndex(index) {
// (mixer.clipAction() will also take a name string if that works better for your setup)
this.action = this.mixer.clipAction(this.animations[index]);
this.action.reset()
this.action.timeScale = 1;
this.action.setLoop(THREE.LoopOnce);
this.action.clampWhenFinished = true;
this.action.play();
}
// assumes that the mixer has already played
playClipReverseByIndex(index) {
// (mixer.clipAction() will also take a name string if that works better for your setup)
this.action = this.mixer.clipAction(this.animations[index]);
this.action.paused = false;
this.action.timeScale = -1;
this.action.setLoop(THREE.LoopOnce);
this.action.play();
}
// will force the mixer to play in reverse no matter what
playClipReverseByIndex_Forced(index) {
this.action = this.mixer.clipAction(this.animations[index]);
if(this.action.time === 0) {
this.action.time = this.action.getClip().duration;
}
this.action.paused = false;
this.action.setLoop(THREE.LoopOnce);
this.action.timeScale = -1;
this.action.play();
}
// Call update in loop
update(delta) {
if(this.mixer) {
this.mixer.update(delta);
}
}
}
@ametthey
Copy link

Hey Harry, thanks a lot for sharing this code. I have been trying to create an animation on a click event, and on another click doing a reverse animation. It's working when I do the animation/animation reverse once but not like everytime I want, you have an idea ??

I'm taking the leap to share my code

This is my GLTF object with morph animation.

loader.load(
            '/wp-content/themes/_themename/dist/assets/images/MORPH-RILES.gltf',
            function ( gltf ) {
                // Base Model
                model = gltf.scene;
                // Base of sub model 1 : top
                rilesTop = gltf.scene.children[0].children[0];
                rilesTop.material.envMap = textureCube;
                rilesTop.material.metalness = 0.2;
                rilesTop.material.roughness = 0.0;
                // Base of sub model 2 : bottom
                rilesBottom = gltf.scene.children[0].children[1];
                rilesBottom.material.envMap = textureCube;
                rilesBottom.material.metalness = 0.2;
                rilesBottom.material.roughness = 0.0;
                // add the model to the scene
                scene.add( model );
                // Animation for the model
                mixer = new THREE.AnimationMixer( model );
                // Store animations
                const clips = gltf.animations;
                // animation 1 :  Morphing
                animMorph = THREE.AnimationClip.findByName( clips, 'Morph.001' );
                actionMorph = mixer.clipAction( animMorph );
                actionMorph.setDuration( 40 );
                actionMorph.loop = THREE.LoopRepeat;
                // animation 2 : Rotation
                animRotation = THREE.AnimationClip.findByName( clips, 'RILESUNDAYZ.001Action' );
                mixer.clipAction( animRotation ).setLoop( THREE.LoopRepeat ).play();
            }
        );

And this is my code when I trigger the animation

let mainLoop = function() {
    renderer.render(scene, camera);
    requestAnimationFrame(mainLoop);
    if (mixer) {
        mixer.update(clock.getDelta());
    }
    if ( rilesTop ) {
        // THIS IS MY FIRST BUTTON
        if ( sigleRSDZ && sigleRSDZ.classList.contains('is-active') ) {
            actionMorph.timeScale = 1;
            actionMorph.setLoop(THREE.LoopOnce);
            actionMorph.clampWhenFinished = true;
            actionMorph.play();
            // console.log( actionMorph );
        // THIS IS MY SECOND BUTTON
        } else if( sigleRILESUNDAYZ && sigleRILESUNDAYZ.classList.contains('is-active') ) {
            actionMorph.paused = false;
            actionMorph.timeScale = -1;
            // actionMorph.setLoop(THREE.LoopRepeat);
            // actionMorph.play();
            // console.log( actionMorph );
            // if (  )
            // console.log('on part sur rilesundayz');
        }
    }
};

I really don't understand why it's only doing the animation once !!!

@rtpHarry
Copy link
Author

It's been a while since I used this code, but the purpose of it was to only do it once. There are line in your code that say actionMorph.setLoop(THREE.LoopOnce); which seems like they are going to cause you issues. It also looks like you are using something different with animMorph coming from clipAction not being a straight mixer like my code example. I'm afraid I can't be much help from looking at your code. My skills in this area have faded out.

@souporserious
Copy link

Just came across wanting to do the same @ametthey, it looks like you just need to call action.reset() before playing the animation on mouse down and it should work as expected.

@mudroljub
Copy link

playClipReverseByIndex_Forced works for me :) thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment