body {
  margin: 0;
  padding: 0;
}
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.122/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.122/examples/jsm/controls/OrbitControls.js";

var camera, controls, scene, renderer, cubes, tween;
var objects = [];

init();
animate();

function init() {
  camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    0.01,
    100000
  );
  camera.position.z = 10;
  camera.position.y = 5;

  scene = new THREE.Scene();

  buildScene();

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.5;
  controls.addEventListener('end', () => {
    updateCameraOrbit();
  });
  updateCameraOrbit();

  renderer.domElement.addEventListener("click", onclick, true);
}

function onclick(event) {
  var selectedObject;
  var raycaster = new THREE.Raycaster();
  var mouse = new THREE.Vector2();
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  var intersects = raycaster.intersectObjects(cubes, true);
  if (intersects.length > 0) {
    selectedObject = intersects[0];
    
    //Todo: Calculate a point that is 5 units nearer to the camera than the target position
    //      so that the camera stops right in front of each clicked pillar
    
     var cameraPosition = camera.position.clone();
     console.log("cameraPosition :  ", logVector3(cameraPosition));
    
     var targetPosition = selectedObject.object.position.clone();
     console.log("targetPosition :  ", logVector3(targetPosition));
    
     var distance = cameraPosition.sub(targetPosition); 
     console.log("distance :  ", logVector3(distance));
    
     var direction = distance.normalize();
     console.log("direction :  ", logVector3(direction));
    
     var offset = distance.clone().sub(direction.multiplyScalar(10.0));
     console.log("offset :  ", logVector3(offset));
    
     var newPos = selectedObject.object.position.clone().sub(offset);
     newPos.y = camera.position.y;
    
    tween = new TWEEN.Tween(camera.position).to(
      {
        x: newPos.x,
        y: newPos.y,
        z: newPos.z
      },
      2000
    );

    tween.easing(TWEEN.Easing.Quadratic.Out);
    tween.start();
    tween.onUpdate(function () {
      updateCameraOrbit();
    }.bind(this));
    tween.onComplete(function () {
      updateCameraOrbit();
    }.bind(this));
  }
}

function updateCameraOrbit() {
  // Update OrbitControls target to a point just in front of the camera

  var forward = new THREE.Vector3();
  camera.getWorldDirection(forward);

  controls.target.copy(camera.position).add(forward);
};



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

function buildScene() {
  var planeGeometry = new THREE.PlaneGeometry(100, 100, 50, 50);
  var material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    wireframe: true
  });
  var plane = new THREE.Mesh(planeGeometry, material);
  plane.rotateX(-Math.PI / 2);
  scene.add(plane);

  cubes = [];
  var mat, geo;
  for (var i = 0; i < 30; i++) {
    geo = new THREE.BoxGeometry(
      Math.random() * 4,
      Math.random() * 16,
      Math.random() * 4
    );
    mat = new THREE.MeshBasicMaterial({ wireframe: false });
    switch (i % 3) {
      case 0:
        mat.color = new THREE.Color(0xff0000);
        break;
      case 1:
        mat.color = new THREE.Color(0xffff00);
        break;
      case 2:
        mat.color = new THREE.Color(0x0000ff);
        break;
    }
    const cube = new THREE.Mesh(geo, mat);
    cubes.push(cube);
  }
  cubes.forEach((c) => {
    c.position.x = Math.random() * 100 - 50;
    c.position.z = Math.random() * 100 - 50;
    c.geometry.computeBoundingBox();
    c.position.y =
      (c.geometry.boundingBox.max.y - c.geometry.boundingBox.min.y) / 2;
    scene.add(c);
  });
}

function logVector3(pos){
  if(pos.x || pos.x === 0){
    return "x:  " + pos.x + "  /  y:  " + pos.y + "  /  z:  " + pos.z;
  }else{
    return pos;
  }
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/tween.js/16.7.0/Tween.js