Move camera with quaternions and tween.js

I want to move the camera with a rotation from one point to another one (following the surface of a sphere). The camera is looking at the center of one object.

I use the following code that works, but I’m not sure if it is correct.

rotateCameraTo (cameraX, cameraY, cameraZ, time) {
    const camera = this._camera.camera;
    const initialCamera = new Vector3().copy(camera.position);

    const cameraVectorStart = new Vector3(camera.position.x, camera.position.y, camera.position.z);
    const cameraVectorEnd = new Vector3(cameraX, cameraY, cameraZ);
    cameraVectorStart.normalize();
    cameraVectorEnd.normalize();

    let qa = new Quaternion(0, 0, 0, 1);
    let qb = new Quaternion().setFromUnitVectors(cameraVectorStart, cameraVectorEnd);
    let qm = new Quaternion();

    let ti = { t: 0 };

    new TWEEN.Tween(ti)
      .to({ t: 1 }, time)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onUpdate(function () {
        camera.position.set(initialCamera.x, initialCamera.y, initialCamera.z);
        qm.slerpQuaternions(qa, qb, ti.t);
        camera.position.applyQuaternion(qm);
        //At the moment the object is centered in the origin
        camera.lookAt(0, 0, 0);
      })
      .onComplete(function () {
        window.cancelAnimationFrame(animationID);
      })
      .start();
  }

Things that I don’t know if there are correct:

  • I set the camera to initial position every .onUpdate().
  • I use let qa = new Quaternion(0, 0, 0, 1) because it works but I’m not sure about this.
  • If there is a better solution than use quaternions

Another doubt is if it is possible to get another path instead the shorter from one point to the other one. This is because I prefer to rotate with Y as a pivot, but If I use points with different values of Y it does not work.

Any suggestions will be greatly appreciated!

1 Like

first off… if you’re getting the behavior you want, then your solution might be fine… until it’s not. :smiley:

An alternative to interpolating/slerping quaternion, is to set up a new point called cameraLookTarget…

Then you tween that point between the different places you want the camera to look at, and each frame, you camera.lookAt(cameraLookTarget);

So then you camera code is kinda reduced to tweening the 2 points… camera position and cameraLookTarget.

I find that setup to be simpler/easier to reason about… but both approaches have their uses.

2 Likes

@Dexylon I think this should be a bit clearer:

rotateCamera (cameraVectorEnd, time) {
        const camera = this._camera.camera;

        const cameraVectorStart = camera.position.clone()

        let qa = new Quaternion(0, 0, 0, 1).normalize();
        let qb = new Quaternion().setFromUnitVectors(cameraVectorStart, cameraVectorEnd).normalize();
        let qm = new Quaternion();

        let ti = { t: 0 };

        new TWEEN.Tween(ti)
          .to({ t: 1 }, time)
          .easing(TWEEN.Easing.Quadratic.InOut)
          .onUpdate(function () {
            camera.position.copy(cameraVectorStart);
            qm.slerpQuaternions(qa, qb, ti.t);
            camera.position.applyQuaternion(qm);
            //Center of the object
            camera.lookAt(0, 0, 0);
          })
          .onComplete(function () {
            window.cancelAnimationFrame(animationID);
          })
          .start();
      }
1 Like

What if I use OrbitControls, where the camera is always looking at the target, but I want to use a button that also starts a movement above the target which would also trigger a gimble lock? Would I need to disable the controls, tween the quaternion, tween the position and then turn it back on?
Sadly when setting orbitControls.enable = false I still can control it which seems to be a bug.

At the moment I have exactly that issue Animation-with-tween-js/18549

In theory, using quaternions avoids gimbal lock. With the code in my post, I achieved rotation without experiencing gimbal lock. Another issue I encountered was that I didn’t want to take the shortest path. One solution was to chain two tweens (with one point more) to modify the path and avoidgo over the model like in this image.


Hopefully it is helpful to you.

1 Like

I came up with that solution to work around the poles.

How did you calculate the intermediate non-direct point?

I don’t remember, it was 1-2 years ago, and I don’t work there anymore so I don’t have access to the code. I think it was related to finding the midpoint and moving it in the X or Z direction based on certain values. However, there should be a better way.

To avoid gimbal lock on the camera, sometimes you can do this:

at init time:
camera.up = camera.up.clone();

in your render loop:

camera.up.set(0,1,0).applyQuaternion(camera.quaternion);
1 Like