<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
    {
        "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three@0.156.1/build/three.module.js",
            "addons/": "https://cdn.jsdelivr.net/npm/three@0.156.1/examples/jsm/"
        }
    }

</script>

<div class="container" id="container"></div>

<div class='distance'>
  <h1 class='distanceText'>
    
  </h1>
</div>
html, body{
    width: 100vw;
    height: 100vh;
    padding: 0;
    margin: 0;
    background-color: black;
    margin: 0;
    padding: 0;
    font-family:'mono45-headline';
}

.container{
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: 100vw;
    padding: 0;
    margin: 0;
    z-index: 3;
  background: linear-gradient(0deg, rgba(74,74,74,0.2) 0%, rgba(173,173,173,0.5) 53%, rgba(240,240,240,0.7) 100%);
}

.distance{
  z-index:10;
  color: #ffffff;
}

.selectBox {
  border: 1px solid #55aaff;
  background-color: rgba(75, 160, 255, 0.3);
  position: fixed;
}
import * as THREE from 'three';

const SelectionBox = (function() {

  var frustum = new THREE.Frustum();
  var center = new THREE.Vector3();

  function SelectionBox(camera, scene, deep) {

    this.camera = camera;
    this.scene = scene;
    this.startPoint = new THREE.Vector3();
    this.endPoint = new THREE.Vector3();
    this.collection = [];
    this.deep = deep || Number.MAX_VALUE;

  }

  SelectionBox.prototype.select = function(startPoint, endPoint) {

    this.startPoint = startPoint || this.startPoint;
    this.endPoint = endPoint || this.endPoint;
    this.collection = [];

    this.updateFrustum(this.startPoint, this.endPoint);
    this.searchChildInFrustum(frustum, this.scene);

    return this.collection;

  };

  SelectionBox.prototype.updateFrustum = function(startPoint, endPoint) {

    startPoint = startPoint || this.startPoint;
    endPoint = endPoint || this.endPoint;

    this.camera.updateProjectionMatrix();
    this.camera.updateMatrixWorld();

    var tmpPoint = startPoint.clone();
    tmpPoint.x = Math.min(startPoint.x, endPoint.x);
    tmpPoint.y = Math.max(startPoint.y, endPoint.y);
    endPoint.x = Math.max(startPoint.x, endPoint.x);
    endPoint.y = Math.min(startPoint.y, endPoint.y);

    var vecNear = this.camera.position.clone();
    var vecTopLeft = tmpPoint.clone();
    var vecTopRight = new THREE.Vector3(endPoint.x, tmpPoint.y, 0);
    var vecDownRight = endPoint.clone();
    var vecDownLeft = new THREE.Vector3(tmpPoint.x, endPoint.y, 0);
    vecTopLeft.unproject(this.camera);
    vecTopRight.unproject(this.camera);
    vecDownRight.unproject(this.camera);
    vecDownLeft.unproject(this.camera);

    var vectemp1 = vecTopLeft.clone().sub(vecNear);
    var vectemp2 = vecTopRight.clone().sub(vecNear);
    var vectemp3 = vecDownRight.clone().sub(vecNear);
    vectemp1.normalize();
    vectemp2.normalize();
    vectemp3.normalize();

    vectemp1.multiplyScalar(this.deep);
    vectemp2.multiplyScalar(this.deep);
    vectemp3.multiplyScalar(this.deep);
    vectemp1.add(vecNear);
    vectemp2.add(vecNear);
    vectemp3.add(vecNear);

    var planes = frustum.planes;

    planes[0].setFromCoplanarPoints(vecNear, vecTopLeft, vecTopRight);
    planes[1].setFromCoplanarPoints(vecNear, vecTopRight, vecDownRight);
    planes[2].setFromCoplanarPoints(vecDownRight, vecDownLeft, vecNear);
    planes[3].setFromCoplanarPoints(vecDownLeft, vecTopLeft, vecNear);
    planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft);
    planes[5].setFromCoplanarPoints(vectemp3, vectemp2, vectemp1);
    planes[5].normal.multiplyScalar(-1);

  };

  SelectionBox.prototype.searchChildInFrustum = function(frustum, object) {

    if (object.isMesh) {

      if (object.material !== undefined) {

        object.geometry.computeBoundingSphere();

        center.copy(object.geometry.boundingSphere.center);

        center.applyMatrix4(object.matrixWorld);

        if (frustum.containsPoint(center)) {

          this.collection.push(object);

        }

      }

    }

    if (object.children.length > 0) {

      for (var x = 0; x < object.children.length; x++) {

        this.searchChildInFrustum(frustum, object.children[x]);

      }

    }

  };

  return SelectionBox;

})();



/**
 * @author HypnosNova / https://www.threejs.org.cn/gallery
 */

const SelectionHelper = (function() {

  function SelectionHelper(selectionBox, renderer, cssClassName) {

    this.element = document.createElement('div');
    this.element.classList.add(cssClassName);
    this.element.style.pointerEvents = 'none';

    this.renderer = renderer;

    this.startPoint = new THREE.Vector2();
    this.pointTopLeft = new THREE.Vector2();
    this.pointBottomRight = new THREE.Vector2();

    this.isDown = false;

    this.renderer.domElement.addEventListener('mousedown', function(event) {

      this.isDown = true;
      this.onSelectStart(event);

    }.bind(this), false);

    this.renderer.domElement.addEventListener('mousemove', function(event) {

      if (this.isDown) {

        this.onSelectMove(event);

      }

    }.bind(this), false);

    this.renderer.domElement.addEventListener('mouseup', function(event) {

      this.isDown = false;
      this.onSelectOver(event);

    }.bind(this), false);

  }

  SelectionHelper.prototype.onSelectStart = function(event) {

    this.renderer.domElement.parentElement.appendChild(this.element);

    this.element.style.left = event.clientX + 'px';
    this.element.style.top = event.clientY + 'px';
    this.element.style.width = '0px';
    this.element.style.height = '0px';

    this.startPoint.x = event.clientX;
    this.startPoint.y = event.clientY;

  };

  SelectionHelper.prototype.onSelectMove = function(event) {

    this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
    this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
    this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
    this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);

    this.element.style.left = this.pointTopLeft.x + 'px';
    this.element.style.top = this.pointTopLeft.y + 'px';
    this.element.style.width = (this.pointBottomRight.x - this.pointTopLeft.x) + 'px';
    this.element.style.height = (this.pointBottomRight.y - this.pointTopLeft.y) + 'px';

  };

  SelectionHelper.prototype.onSelectOver = function() {

    this.element.parentElement.removeChild(this.element);

  };

  return SelectionHelper;

})();



var container;
var camera, scene, renderer;

init();
animate();

function init() {

  container = document.querySelector('.container');
  document.body.appendChild(container);

  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
  camera.position.z = 1000;

  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xf0f0f0);

  scene.add(new THREE.AmbientLight(0x505050));

  var light = new THREE.SpotLight(0xffffff, 1.5);
  light.position.set(0, 500, 2000);
  light.angle = Math.PI / 9;

  light.castShadow = true;
  light.shadow.camera.near = 1000;
  light.shadow.camera.far = 4000;
  light.shadow.mapSize.width = 1024;
  light.shadow.mapSize.height = 1024;

  scene.add(light);

  var geometry = new THREE.BoxGeometry(20, 20, 20);

  for (var i = 0; i < 200; i++) {

    var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
      color: Math.random() * 0xffffff
    }));

    object.position.x = Math.random() * 1600 - 800;
    object.position.y = Math.random() * 900 - 450;
    object.position.z = Math.random() * 900 - 500;

    object.rotation.x = Math.random() * 2 * Math.PI;
    object.rotation.y = Math.random() * 2 * Math.PI;
    object.rotation.z = Math.random() * 2 * Math.PI;

    object.scale.x = Math.random() * 2 + 1;
    object.scale.y = Math.random() * 2 + 1;
    object.scale.z = Math.random() * 2 + 1;

    object.castShadow = true;
    object.receiveShadow = true;

    scene.add(object);

  }

  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFShadowMap;

  container.appendChild(renderer.domElement);

  window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);

}

//

function animate() {

  requestAnimationFrame(animate);

  render();

}

function render() {

  renderer.render(scene, camera);

}

var selectionBox = new SelectionBox(camera, scene);
var helper = new SelectionHelper(selectionBox, renderer, 'selectBox');

function zoomToSelection() {
  let topLeft = helper.pointTopLeft;
  let bottomRight = helper.pointBottomRight;

  // Get the centerpoint of the selection.
  let center = new THREE.Vector2(topLeft.x + (bottomRight.x - topLeft.x) / 2, topLeft.y + (bottomRight.y - topLeft.y) / 2);

  if(center.x < 10 || center.z < 10) return;
  // Get the center position in world space.
  var vector = new THREE.Vector3(
      (center.x / window.innerWidth) * 2 - 1,
      -(center.y / window.innerHeight) * 2 + 1,
      0.5
  );
  vector.unproject(camera);
  camera.lookAt(vector);
  var movement = vector.clone();

  // Get the ratio between the box size and the window.
  let zoomNeeded = (bottomRight.y - topLeft.y) / window.innerHeight;
  // Get a scalar by which to move the camera in the direction it's looking.
  let distanceToOrigin = camera.position.distanceTo(new THREE.Vector3(0, 0, 0))
  
  let distance = distanceToOrigin - distanceToOrigin * zoomNeeded;

  movement.sub(camera.position).normalize().multiplyScalar(-distance);
  var toDirection = camera.position.clone().sub(movement);
  camera.position.set(toDirection.x, toDirection.y, toDirection.z);
}

document.addEventListener('mousedown', function(event) {

  for (var item of selectionBox.collection) {

    item.material.emissive = new THREE.Color(0x000000);

  }

  selectionBox.startPoint.set(
    (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
    0.5);

});

document.addEventListener('mousemove', function(event) {

  if (helper.isDown) {

    for (var i = 0; i < selectionBox.collection.length; i++) {

      selectionBox.collection[i].material.emissive = new THREE.Color(0x000000);

    }

    selectionBox.endPoint.set(
      (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
      0.5);

    var allSelected = selectionBox.select();

    for (var i = 0; i < allSelected.length; i++) {

      allSelected[i].material.emissive = new THREE.Color(0x0000ff);

    }

  }

});

document.addEventListener('mouseup', function(event) {

  selectionBox.endPoint.set(
    (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
    0.5);

  var allSelected = selectionBox.select();

  for (var i = 0; i < allSelected.length; i++) {

    allSelected[i].material.emissive = new THREE.Color(0x0000ff);

  }
  
  zoomToSelection();

});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.