Skip to content

Instantly share code, notes, and snippets.

@rtpHarry

rtpHarry/animation.js

Last active Mar 23, 2021
Embed
What would you like to do?
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

This comment has been minimized.

Copy link

@ametthey ametthey commented Jan 14, 2021

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

This comment has been minimized.

Copy link
Owner Author

@rtpHarry rtpHarry commented Jan 14, 2021

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.

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