Animating perspective camera to mimic orbit controls

I’m trying to animate the camera on click to rotate around an object at 0,0,0. I’ve tried the setup shown below using TWEEN but the camera seems to move instead of rotating around. I also tried using an AnimationClip with position + rotation of the camera but the linear interpolation causes the camera to move linearly from point A to B instead of in a curve. Is there any way to achieve my desired functionality?

 function createTween(){
              var qa = new THREE.Quaternion().copy(camera.quaternion);
              var qb = destinationQuaternion;
              var qm = new THREE.Quaternion();
              let r = new TWEEN.Tween({t: 0})
              .to({t: 1}, 5000)
              .onUpdate((tween) => {
                  THREE.Quaternion.slerp(qa, qb, qm, tween.t);
                  camera.quaternion.set(qm.x, qm.y, qm.z, qm.w);
              })
              .easing(TWEEN.Easing.Quartic.InOut)
              .start();
            }

Right now, you animate the quaternion property which means you only rotate the camera. To get the same orbiting behavior like OrbitControls, you have to translate the camera in 3D space and ensure by calling lookAt() to look at the focus point.

The problem is that animation library like Tween.js and GSAP will animate in a linear fashion from vector A to B. E.g. three.js dev template - module - JSFiddle - Code Playground

If you want to animate the camera around an object by simulating the trajectory of a satellite, you have to animate along a predefined sequence of points (A-B-C-D etc.).

Thanks for the feedback. I looked around on the web and found a solution to a problem similar to mine. After changing a few bits I got this code to rotate the camera properly.

  function cameraRotation(){
  var sphericalStart = new THREE.Spherical();
  var sphericalEnd = new THREE.Spherical();
  var vectorStart = new THREE.Vector3();
  var vectorEnd = new THREE.Vector3();
  var angle = {
    value: 0
  };
  var angleEnd = {
    value: 0
  };
  var normal = new THREE.Vector3();

  function setInitials() {
    sphericalStart.setFromCartesianCoords(positions[0].x, positions[0].y, positions[0].z);
    sphericalEnd.setFromCartesianCoords(positions[1].x, positions[1].y, positions[1].z);
    vectorStart.setFromSpherical(sphericalStart);
    vectorEnd.setFromSpherical(sphericalEnd);
    normal.copy(vectorStart).cross(vectorEnd).normalize();
    angle.value = 0;
    angleEnd.value = vectorStart.angleTo(vectorEnd);
  }
  setInitials();


  var tween = new TWEEN.Tween(angle).to(angleEnd, 5000)
  .onUpdate(
    () => {
      camera.position.copy(vectorStart).applyAxisAngle(normal, angle.value)
      camera.lookAt(0,0,0)
    }
  )

It only doesn’t work if the second camera is zoomed, but I will attempt to solve this by creating an animationClip or another tween for zoom.

3 Likes

Thx @skiler07 ! This helped me a lot. This can be simplified a bit though. If anybody needs it:

function rotateCamera(from, to) {

  const angle = { value: 0 };
  const angleEnd = { value: 0 };
  const normal = new Vector3();

  normal.copy(from).cross(to).normalize();
  angleEnd.value = from.angleTo(to);

  const tween = new Tween(angle).to(angleEnd, 500).onUpdate(() => {
    camera.position.copy(from).applyAxisAngle(normal, angle.value);
    camera.lookAt(0, 0, 0);
  });
  tween.start();
1 Like

Thank you so much, this really helped me after days of searching,

Remember to add TWEEN.update(); in your animation update function.