Source: minimap.js

const THREE = require('three');

/**
 * This provide a full scale minimap. It will always
 * display the whole map.
 * 
 * @class
 * @author Alan Wu
 * @return {Minimap}
 */
exports.Minimap = function (sceneIn) {
  let targetScene = sceneIn;
  this.camera = new THREE.OrthographicCamera(
    -0.5, 0.5, 0.5, -0.5, 0.01, 10);
  this.helper = undefined;
  let geometry = new THREE.BufferGeometry();
  var vertices = new Float32Array( [
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
     1.0,  1.0,  1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0, -1.0,  1.0
  ] );
  let positionAttributes = new THREE.BufferAttribute( vertices, 3 );
  geometry.setAttribute( 'position', positionAttributes);
  var material = new THREE.MeshBasicMaterial( { color: 0x333333, 
    depthTest: false,
    depthWrite: false,
    opacity: 0.5,
    transparent: true } );
  this.mask = new THREE.Mesh( geometry, material );
  let _box = new THREE.Box3();
  let _center = new THREE.Vector3();

  this.getDiffFromNormalised = (x, y) => {
    _box.setFromBufferAttribute(positionAttributes).getCenter(_center);
    let coord = _center.clone().project(this.camera);
    let new_coord = new THREE.Vector3(x, y, coord.z).unproject(this.camera);
    return new_coord.sub(_center);
  }

  let setCurrentCameraSettings = (diameter, newViewport)  => {
    if (targetScene.camera.near)
      this.camera.near = targetScene.camera.near;
    if (newViewport.farPlane)
      this.camera.far = newViewport.farPlane;
    if (newViewport.eyePosition)
      this.camera.position.set(newViewport.eyePosition[0],
        newViewport.eyePosition[1], newViewport.eyePosition[2]);
    if (newViewport.upVector)
      this.camera.up.set(newViewport.upVector[0], newViewport.upVector[1],
        newViewport.upVector[2]);
    if (newViewport.targetPosition)
      this.camera.lookAt(new THREE.Vector3(newViewport.targetPosition[0],
        newViewport.targetPosition[1], newViewport.targetPosition[2]));
    this.camera.zoom = 1 / diameter;
    this.camera.updateProjectionMatrix();
  }

  this.getBoundary = () => {
    let target = new THREE.Vector3().copy(
      targetScene.camera.target).project(targetScene.camera);
    let v1 = new THREE.Vector3(-1, -1, target.z).unproject(targetScene.camera);
    let v2 = new THREE.Vector3(1, -1, target.z).unproject(targetScene.camera);
    let v3 = new THREE.Vector3(1, 1, target.z).unproject(targetScene.camera);
    let v4 = new THREE.Vector3(-1, 1, target.z).unproject(targetScene.camera);
    let array = [v1, v2, v3, v3, v4, v1];
    positionAttributes.copyVector3sArray(array);
    positionAttributes.needsUpdate = true;
  }

  this.updateCamera = () => {
    this.getBoundary();
    let cameraControl = targetScene.getZincCameraControls();
    let boundingBox = targetScene.getBoundingBox();
    if (boundingBox) {
      // enlarge radius to keep image within edge of window
      const diameter = boundingBox.min.distanceTo(boundingBox.max);
      const radius = diameter / 2.0;
      const centreX = (boundingBox.min.x + boundingBox.max.x) / 2.0;
      const centreY = (boundingBox.min.y + boundingBox.max.y) / 2.0;
      const centreZ = (boundingBox.min.z + boundingBox.max.z) / 2.0;
      const clip_factor = 4.0;
      const viewport = cameraControl.getViewportFromCentreAndRadius(
        centreX, centreY, centreZ, radius, 40, radius * clip_factor);
      setCurrentCameraSettings(diameter, viewport);
    }
  }
}