<div id='canvas'></div>
#canvas {
  width: 100%;
  height: 100vh;
  border: 1px #000 solid;
}
let isDown = false;

/*lasso parameter*/
const tempVec0 = new THREE.Vector2();
const tempVec1 = new THREE.Vector2();
const tempVec2 = new THREE.Vector2();

let prevX = -Infinity;
let prevY = -Infinity;

const selectionPoints = [];
let selectionShapeNeedsUpdate = false;
let selectionNeedsUpdate = false;
let selectionShape;

function init() {
  var pW = $("#canvas").width();
  var pH = $("#canvas").height();

  scene = new THREE.Scene();

  selectionShape = new THREE.Line();
  selectionShape.material.color.set(0xE6FF00).convertSRGBToLinear();
  selectionShape.renderOrder = 1;
  selectionShape.position.z = -2;
  selectionShape.depthTest = false;
  selectionShape.scale.setScalar(1);
  
  camera = new THREE.PerspectiveCamera(45, pW / pH, 1, 100);
  camera.position.z = 2;
  //camera.updateProjectionMatrix();
  camera.add(selectionShape);
    
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(pW, pH);
  $("#canvas").append(renderer.domElement);
  
 /*
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.noRotate = true;
  controls.enablePan = false;
  controls.update();
   
  controls.minDistance = 3;
 

 
  controls.enableDamping = false;
  controls.noRotate = true;
  controls.noPan = true;
  controls.zoomSpeed = 1;
  controls.update(); 
  */
  
  renderer.domElement.addEventListener("pointerdown", e => {
    prevX = e.clientX;
    prevY = e.clientY;

    isDown = true;
    selectionPoints.length = 0;
  });

  renderer.domElement.addEventListener("pointermove", e => {
    if (isDown) {

      const ex = e.clientX;
      const ey = e.clientY;

      var domRect = renderer.domElement.getBoundingClientRect();
      nx = ((e.clientX - domRect.left) / (domRect.right - domRect.left)) * 2 - 1;
      ny = -((e.clientY - domRect.top) / (domRect.bottom - domRect.top)) * 2 + 1;
      
      // If the mouse hasn't moved a lot since the last point
      if (Math.abs(ex - prevX) >= 3 || Math.abs(ey - prevY) >= 3) {
        const i = selectionPoints.length / 3 - 1;
        const i3 = i * 3;
        let doReplace = false;
        
        if (selectionPoints.length > 3) {
          // prev segment direction
          tempVec0.set(selectionPoints[i3 - 3], selectionPoints[i3 - 3 + 1]);
          tempVec1.set(selectionPoints[i3], selectionPoints[i3 + 1]);
          tempVec1.sub(tempVec0).normalize();

          // this segment direction
          tempVec0.set(selectionPoints[i3], selectionPoints[i3 + 1]);
          tempVec2.set(nx, ny);
          tempVec2.sub(tempVec0).normalize();

          const dot = tempVec1.dot(tempVec2);
          doReplace = dot > 0.99;
        }

        if (doReplace) {
          selectionPoints[i3] = nx; //nx;
          selectionPoints[i3 + 1] = ny; //ny;
        } else {
          //selectionPoints.push(nx, ny, 0);
          selectionPoints.push(nx, ny, 0);
        }

        selectionShapeNeedsUpdate = true;
        selectionShape.visible = true;

        prevX = nx;
        prevY = ny;

        selectionNeedsUpdate = true;
      }
    }
  });

  renderer.domElement.addEventListener("pointerup", e => {
    isDown = false;    
    return false;
  });
}

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  
  const ogLength = selectionPoints.length;
  selectionPoints.push(selectionPoints[0], selectionPoints[1], selectionPoints[2]);

  selectionShape.geometry.setAttribute('position', new THREE.Float32BufferAttribute(selectionPoints, 3, false));
  selectionPoints.length = ogLength;

  if (selectionNeedsUpdate) {

    selectionNeedsUpdate = false;
    if (selectionPoints.length > 0) {
        updateSelection();
    }
  }
  
  const yScale = Math.tan(THREE.MathUtils.DEG2RAD * camera.fov / 2) * -2;
  selectionShape.scale.set(- yScale * camera.aspect, - yScale, 1);
}

function updateSelection() {
   scene.add(selectionShape);
}

$(function () {
  init();
  render();

  window.addEventListener("resize", function () {
    camera.aspect = $("#canvas").width() / $("#canvas").height();
    camera.updateProjectionMatrix();
    renderer.setSize($("#canvas").width(), $("#canvas").height());
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.