Search code examples
javascriptloopsthree.jstween.js

Reverse the effects of a Tween.js animation


I'm trying to animate a three.js block in such a way that it returns to its original position when the animation ends, using tween.js.

Is there a way to achieve this with tween.js only using one tween?

I have got this working as shown below:

var position = {x: -200, y: 150, width: 1, height: 1, depth: 1, rotx: -0.5, roty: 0.7, rotz: 0.9};
var target = {x: 200, y: -100, width: 0.4, height: 3, depth: 8, rotx: 0.3, roty: -0.4, rotz: -0.6};
var position2 = {x: -200, y: 150, width: 1, height: 1, depth: 1, rotx: -0.5, roty: 0.7, rotz: 0.9};

var mesh = new THREE.Mesh(
  new THREE.CubeGeometry(190, 45, 30),
  new THREE.MeshBasicMaterial({color: 0x444444}),
  0
);
mesh.position.set(position.x, position.y, 0);
mesh.rotation.set(position.rotx, position.roty, position.rotz);
scene.add(mesh);

var t1 = new TWEEN.Tween(position).to(target, 2000);
t1.onUpdate(function() {
  mesh.position.set(position.x, position.y, 0);
  mesh.scale.set(position.width, position.height, position.depth);
  mesh.rotation.set(position.rotx, position.roty, position.rotz);
});
t1.easing(TWEEN.Easing.Quadratic.Out);
t1.onComplete(function() {t2.start();});

var t2 = new TWEEN.Tween(target).to(position2, 2000);
t2.onUpdate(function() {
  mesh.position.set(target.x, target.y, 0);
  mesh.scale.set(target.width, target.height, target.depth);
  mesh.rotation.set(target.rotx, target.roty, target.rotz);
});
t2.easing(TWEEN.Easing.Quadratic.In);

t1.start();

And I have the tweens updating in my animation function:

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
  mesh.__dirtyPosition = true;
  mesh.__dirtyRotation = true;
  TWEEN.update();
}
animate();

This is working as I expect it to, but it is clearly very inefficient, and difficult to work around.

Any and all help will be appreciated.


Solution

  • You're overcomplicating things a bit by re-naming the x, y, z properties to width, height, depth or rotx, roty, rotz. This only means you have to manually translate these properties onUpdate when you do scale.x = position.width and rotation.x = position.rotx. I recommend you keep x, y, z, to avoid these repetitive assignments.

    // We set our start and target pos using the THREE.js "x, y, z" nomenclature
    var startPos = {x: -200, y: 150, z: 0};
    var targetPos = {x: 200, y: -100, z: 0};
    
    // Scale also is defined in "x, y, z"
    var startScale = {x: 1, y: 1, z: 1};
    var targetScale = {x: 0.4, y: 3, z: 8};
    
    // Rotation also has "x, y, z" degrees in Euler angles
    var startRot = {x: -0.5, y: 0.7, z: 0.9};
    var targetRot = {x: 0.3, y: -0.4, z: -0.6};
    
    // Standard mesh setup
    var mesh = new THREE.Mesh(
      new THREE.CubeGeometry(190, 45, 30),
      new THREE.MeshBasicMaterial({color: 0x444444})
    );
    mesh.position.copy(startPos);
    mesh.rotation.copy(startRot);
    scene.add(mesh);
    
    // Create shortcuts for shorter easing names
    var QuadOut = TWEEN.Easing.Quadratic.Out;
    var QuadIn = TWEEN.Easing.Quadratic.In;
    
    // Create one tween for position
    // Notice that you can chain the animation 
    // back to startPos by doing double ".to().to()""
    var t1 = new TWEEN.Tween(mesh.position)
        .to(targetPos, 2000, QuadOut)
        .to(startPos, 2000, QuadIn);
    
    // Second, we tween the mesh's rotation
    var t2 = new TWEEN.Tween(mesh.rotation)
        .to(targetRot, 2000, QuadOut)
        .to(startRot, 2000, QuadIn);
    
    // Third, we tween the mesh's scale
    var t3 = new TWEEN.Tween(mesh.scale)
        .to(targetScale, 2000, QuadOut)
        .to(startScale, 2000, QuadIn);
    
    t1.start();
    t2.start();
    t3.start();
    

    And finally, during animate(), you no longer have to change __dirtyPosition or anything, because the tween is updating the mesh's properties directly.

    function animate() {
        requestAnimationFrame(animate);
        TWEEN.update();
        renderer.render(scene, camera);
    }
    animate();