Search code examples
three.jstween.js

Three and Tween.js - unwanted spinning, is this gimbal lock?


I'm tweening an Object from here

Position (X:82.22, Y:-8.31, Z:57.75)
Rotation (X:-3.00, Y:-0.95, Z:-3.02)

to here

Position (X:57.36, Y:-8.31, Z:93.78)
Rotation (X:-3.05, Y:-0.55, Z:-3.10)

with this tween

            behaviour.tween = new TWEEN.Tween(behaviour.origin).to(behaviour.target,behaviour.offsetTime * 1000)
            .onUpdate(function(){

                hotspot.position.x = behaviour.origin.pX;
                hotspot.position.y = behaviour.origin.pY;
                hotspot.position.z = behaviour.origin.pZ;
                hotspot.rotation.x = behaviour.origin.rX;
                hotspot.rotation.y = behaviour.origin.rY;
                hotspot.rotation.z = behaviour.origin.rZ;
                hotspot.scale.set(behaviour.origin.scale,behaviour.origin.scale,behaviour.origin.scale);
                hotspot.opacity = behaviour.origin.opacity;

            }).

And the object spins along the z axis as it moves across the scene.

Is this likely to be Gimbal lock? If so, what's the way to get around this?


Solution

  • It's always safer to use Quaternion for transitions. For me it works very well without using slerp, but just using Tween.js.

    // excerpt from my code
    var q = obj.quaternion.clone().multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), theta));
    
    new TWEEN.Tween(obj.quaternion)
      .to(q, time)
      .onUpdate(function () { object.quaternion.copy(this); }) // onUpdate isn't really needed in this case
      .start();
    

    So, for you, it should work with something like this:

    behaviour.origin.position = new THREE.Vector3(82.22, -8.31, 57.75);
    behaviour.origin.quaternion = ew THREE.Quaternion().setFromEuler(new THREE.Euler(-3.00, -0.95, 3.02));
    
    behaviour.target.position = new THREE.Vector(57.36, -8.31, 93.78);
    behaviour.target.quaternion = ew THREE.Quaternion().setFromEuler(new THREE.Euler(3.05, -0.55, 3.10));
    
    new TWEEN.Tween(behaviour.origin)
      .to(behaviour.target, behaviour.offsetTime * 1000)
      .onUpdate(function () {
        hotspot.position.copy(behaviour.origin.position);
        hotspot.quaternion.copy(behaviour.origin.quaternion);
      })
      .start();
    

    UPDATE: Ok, it is adviced to use quaternion slerp(), otherwise it can result strange behaviours, too. Hence, I suppose, the code should look like this:

    behaviour.origin.position = new THREE.Vector3(82.22, -8.31, 57.75);
    behaviour.origin.quaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(-3.00, -0.95, 3.02));
    
    behaviour.target.position = new THREE.Vector(57.36, -8.31, 93.78);
    behaviour.target.quaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(3.05, -0.55, 3.10));
    
    behaviour.origin.t = 0;
    behaviour.target.t = 1;
    
    new TWEEN.Tween(behaviour.origin)
      .to(behaviour.target, behaviour.offsetTime * 1000)
      .onUpdate(function () {
        hotspot.position.copy(behaviour.origin.position);
        THREE.Quaternion.slerp(behaviour.origin.quaternion, behaviour.target.quaternion, hotspot.quaternion, behaviour.origin.t);
      })
      .start();