Search code examples
three.js3dcamerarotationlinear-algebra

How do you maintain make a camera move to a new position smoothy while still looking at a target?


In this question, I asked about how one would moves a camera to a different position relative to a target. A very kind gentleman @TheJim01 provided a veery nice answer.

However, I am getting issues with this where the camera is rotated and positioned strangely after I move it.

What are the strategies to keep the camera rotated and maintaining its view of the target smoothly?

threejs-camera-target


Solution

  • You can use any tweening library (Tween.js, GSAP) to move your camera smoothly from current position to the position atop of the sphere.

    Also, use THREE.Spherical() to compute the final point. And don't forget to use .makeSafe() method.

    body {
      overflow: hidden;
      margin: 0;
    }
    button{
      position: absolute;
      font-weight: bold;
    }
    <button id="moveUp">MOVE UP</button>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
    <script type="module">
    import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js";
    import { OrbitControls } from "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js";
    
    let scene = new THREE.Scene();
    scene.fog = new THREE.Fog(0x000000, 6, 15);
    let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
    camera.position.set(0, 0, 10);
    let renderer = new THREE.WebGLRenderer();
    renderer.setSize(innerWidth, innerHeight);
    document.body.appendChild(renderer.domElement);
    
    let controls = new OrbitControls(camera, renderer.domElement);
    controls.maxDistance = 10;
    controls.minDistance = 7;
    
    let sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(4, 36, 18), new THREE.MeshBasicMaterial({color: "aqua", wireframe: true}));
    scene.add(sphere);
    
    moveUp.addEventListener("click", onButtonClick);
    
    let spherical = new THREE.Spherical();
    let startPos = new THREE.Vector3();
    let endPos = new THREE.Vector3();
    let axis = new THREE.Vector3();
    let tri = new THREE.Triangle();
    
    function onButtonClick(event){
      spherical.setFromVector3(camera.position);
      spherical.phi = 0;
      spherical.makeSafe(); // important thing, see the docs for what it does
      endPos.setFromSpherical(spherical);
      
      startPos.copy(camera.position);
      
      tri.set(endPos, scene.position, startPos);
      tri.getNormal(axis);
      
      let angle = startPos.angleTo(endPos);
      
      let value = {value: 0};
      gsap.to(value, {value: 1, 
        duration: 2,
        onUpdate: function(){
          camera.position.copy(startPos).applyAxisAngle(axis, angle * value.value);
          controls.update();
        },
        onStart: function(){
          moveUp.disabled = true;
        },
        onComplete: function(){
          moveUp.disabled = false;
        }
        })
        .play();/**/
    }
    
    renderer.setAnimationLoop(()=>{
      renderer.render(scene, camera);
    });
    </script>