JSFiddle - React, Tailwind, and code Playground

by Paul West

HTML

<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/curves/NURBSCurve.js"></script>
<script src="https://threejs.org/examples/js/curves/NURBSUtils.js"></script>
<script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
<script>
  function getCurveGeometry(points, smoothness, weight) {
    var nurbsControlPoints = [];
    var nurbsKnots = [];
    var nurbsDegree = 2;
    for (let i = 0; i <= nurbsDegree; i++) {
      nurbsKnots.push(0);
    }
    for (let i = 0, j = points.length; i < j; i++) {
      nurbsControlPoints.push(
        new THREE.Vector4(
          points[i].x,
          points[i].y,
          points[i].z,
          i === 1 ? weight : 1
        )
      );
      let knot = (i + 1) / (j - nurbsDegree);
      nurbsKnots.push(THREE.Math.clamp(knot, 0, 1));
    }
    let nurbsCurve = new THREE.NURBSCurve(nurbsDegree, nurbsKnots, nurbsControlPoints);
    return new THREE.BufferGeometry().setFromPoints(nurbsCurve.getPoints(smoothness));
  }

</script>

CSS

body {
  overflow: hidden;
  margin: 0;
}

JavaScript

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controlPoints = [],
  currentPoint,
  plane = new THREE.Plane(),
  planeNormal = new THREE.Vector3(0, 0, 1),
  dragging = false,
  raycaster = new THREE.Raycaster(),
  mouse = new THREE.Vector2(),
  intersects,
  intersectShift = new THREE.Vector3(),
  planePoint = new THREE.Vector3(),
  weightObj = {
    weight: 1
  };

var datGUI = new dat.GUI();
datGUI.add(weightObj, 'weight', -1, 2).onChange(setGeometry);

createPoint(new THREE.Vector3(-2, 0, 0), 0xff0000);
createPoint(new THREE.Vector3(0, 2, 0), 0x00ff00);
createPoint(new THREE.Vector3(2, 0, 0), 0x0000ff);

var curve = new THREE.Line(createCurve(), new THREE.LineBasicMaterial({
  color: "aqua"
}));
scene.add(curve);

function createCurve() {
  return getCurveGeometry([controlPoints[0].position, controlPoints[1].position, controlPoints[2].position], 12, weightObj.weight);
}

window.addEventListener("mousedown", onMouseDown, false);

function onMouseDown(event) {
  setMouse(event);
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObjects(controlPoints);
  if (intersects.length === 0) return;
  currentPoint = intersects[0].object;
  currentPoint.worldToLocal(intersectShift.copy(intersects[0].point));
  plane.setFromNormalAndCoplanarPoint(planeNormal, intersects[0].point);
  dragging = true;
}

window.addEventListener("mouseup", onMouseUp, false);

function onMouseUp() {
  dragging = false;
}

window.addEventListener("mousemove", onMouseMove, false);

function onMouseMove(event) {
  if (!dragging) return;
  setMouse(event);
  raycaster.setFromCamera(mouse, camera);
  raycaster.ray.intersectPlane(plane, planePoint);
 ...