Catmull Spline Editor

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Verge3D webgl - geometry - catmull spline editor</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
      body {
        font-family: Monospace;
        background-color: #f0f0f0;
        margin: 0px;
        overflow: hidden;
      }
      #info {
        position: absolute;
        top: 0px;
        width: 100%;
        padding: 5px;
        font-family:Monospace;
        font-size:13px;
        text-align:center;
      }
    </style>
  </head>
  <body>

    <div id="container"></div>
    <div id="info"><a href="https://www.soft8soft.com/verge3d" target="_blank" rel="noopener">Verge3D</a> - geometry - catmull spline editor</div>

    <script src="../build/v3d.js"></script>

    <script src="js/controls/DragControls.js"></script>
    <script src="js/controls/OrbitControls.js"></script>
    <script src="js/controls/TransformControls.js"></script>

    <script src="js/libs/stats.min.js"></script>
    <script src="js/libs/dat.gui.min.js"></script>

    <script>
      String.prototype.format = function() {
        var str = this;
        for (var i = 0; i < arguments.length; i++) {
          str = str.replace('{' + i + '}', arguments[i]);
        }
        return str;
      };
      var container, stats;
      var camera, scene, renderer;
      var splineHelperObjects = [];
      var splinePointsLength = 4;
      var positions = [];
      var point = new v3d.Vector3();
      var geometry = new v3d.BoxBufferGeometry(20, 20, 20);
      var transformControl;
      var ARC_SEGMENTS = 200;
      var splines = {};
      var params = {
        uniform: true,
        tension: 0.5,
        centripetal: true,
        chordal: true,
        addPoint: addPoint,
        removePoint: removePoint,
        exportSpline: exportSpline
      };
      init();
      animate();
      function init() {
        container = document.getElementById('container');
        scene = new v3d.Scene();
        scene.background = new v3d.Color(0xf0f0f0);
        camera = new v3d.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.set(0, 250, 1000);
        scene.add(camera);
        scene.add(new v3d.AmbientLight(0xf0f0f0));
        var light = new v3d.SpotLight(0xffffff, 1.5);
        light.position.set(0, 1500, 200);
        light.castShadow = true;
        light.shadow = new v3d.LightShadow(new v3d.PerspectiveCamera(70, 1, 200, 2000));
        light.shadow.bias = - 0.000222;
        light.shadow.mapSize.width = 1024;
        light.shadow.mapSize.height = 1024;
        scene.add(light);
        var planeGeometry = new v3d.PlaneBufferGeometry(2000, 2000);
        planeGeometry.rotateX(- Math.PI / 2);
        var planeMaterial = new v3d.ShadowMaterial({ opacity: 0.2 });
        var plane = new v3d.Mesh(planeGeometry, planeMaterial);
        plane.position.y = - 200;
        plane.receiveShadow = true;
        scene.add(plane);
        var helper = new v3d.GridHelper(2000, 100);
        helper.position.y = - 199;
        helper.material.opacity = 0.25;
        helper.material.transparent = true;
        scene.add(helper);
        //var axes = new v3d.AxesHelper(1000);
        //axes.position.set(- 500, - 500, - 500);
        //scene.add(axes);
        renderer = new v3d.WebGLRenderer({ antialias: true });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        container.appendChild(renderer.domElement);
        stats = new Stats();
        container.appendChild(stats.dom);
        var gui = new dat.GUI();
        gui.add(params, 'uniform');
        gui.add(params, 'tension', 0, 1).step(0.01).onChange(function(value) {
          splines.uniform.tension = value;
          updateSplineOutline();
  });
        gui.add(params, 'centripetal');
        gui.add(params, 'chordal');
        gui.add(params, 'addPoint');
        gui.add(params, 'removePoint');
        gui.add(params, 'exportSpline');
        gui.open();
        // Controls
        var controls = new v3d.OrbitControls(camera, renderer.domElement);
        controls.damping = 0.2;
        controls.addEventListener('change', render);
        controls.addEventListener('start', function() {
          cancelHideTransorm();
        });
        controls.addEventListener('end', function() {
          delayHideTransform();
        });
        transformControl = new v3d.TransformControls(camera, renderer.domElement);
        transformControl.addEventListener('change', render);
        transformControl.addEventListener('dragging-changed', function(event) {
          controls.enabled = ! event.value;
  });
        scene.add(transformControl);
        // Hiding transform situation is a little in a mess :()
        transformControl.addEventListener('change', function() {
          cancelHideTransorm();
        });
        transformControl.addEventListener('mouseDown', function() {
          cancelHideTransorm();
        });
        transformControl.addEventListener('mouseUp', function() {
          delayHideTransform();
        });
        transformControl.addEventListener('objectChange', function() {
          updateSplineOutline();
        });
        var dragcontrols = new v3d.DragControls(splineHelperObjects, camera, renderer.domElement); //
        dragcontrols.enabled = false;
        dragcontrols.addEventListener('hoveron', function(event) {
          transformControl.attach(event.object);
          cancelHideTransorm();
        });
        dragcontrols.addEventListener('hoveroff', function() {
          delayHideTransform();
        });
        var hiding;
        function delayHideTransform() {
          cancelHideTransorm();
          hideTransform();
        }
        function hideTransform() {
          hiding = setTimeout(function() {
            transformControl.detach(transformControl.object);
          }, 2500);
        }
        function cancelHideTransorm() {
          if (hiding) clearTimeout(hiding);
        }
        /*******
         * Curves
         *********/
        for (var i = 0; i < splinePointsLength; i++) {
          addSplineObject(positions[i]);
        }
        positions = [];
        for (var i = 0; i < splinePointsLength; i++) {
          positions.push(splineHelperObjects[i].position);
        }
        var geometry = new v3d.BufferGeometry();
        geometry.addAttribute('position', new v3d.BufferAttribute(new Float32Array(ARC_SEGMENTS * 3), 3));
        var curve = new v3d.CatmullRomCurve3(positions);
        curve.curveType = 'catmullrom';
        curve.mesh = new v3d.Line(geometry.clone(), new v3d.LineBasicMaterial({
          color: 0xff0000,
          opacity: 0.35
        }));
        curve.mesh.castShadow = true;
        splines.uniform = curve;
        curve = new v3d.CatmullRomCurve3(positions);
        curve.curveType = 'centripetal';
        curve.mesh = new v3d.Line(geometry.clone(), new v3d.LineBasicMaterial({
          color: 0x00ff00,
          opacity: 0.35
        }));
        curve.mesh.castShadow = true;
        splines.centripetal = curve;
        curve = new v3d.CatmullRomCurve3(positions);
        curve.curveType = 'chordal';
        curve.mesh = new v3d.Line(geometry.clone(), new v3d.LineBasicMaterial({
          color: 0x0000ff,
          opacity: 0.35
        }));
        curve.mesh.castShadow = true;
        splines.chordal = curve;
        for (var k in splines) {
          var spline = splines[k];
          scene.add(spline.mesh);
        }
        load([new v3d.Vector3(289.76843686945404, 452.51481137238443, 56.10018915737797),
          new v3d.Vector3(- 53.56300074753207, 171.49711742836848, - 14.495472686253045),
          new v3d.Vector3(- 91.40118730204415, 176.4306956436485, - 6.958271935582161),
          new v3d.Vector3(- 383.785318791128, 491.1365363371675, 47.869296953772746)]);
      }
      function addSplineObject(position) {
        var material = new v3d.MeshLambertMaterial({ color: Math.random() * 0xffffff });
        var object = new v3d.Mesh(geometry, material);
        if (position) {
          object.position.copy(position);
        } else {
          object.position.x = Math.random() * 1000 - 500;
          object.position.y = Math.random() * 600;
          object.position.z = Math.random() * 800 - 400;
        }
        object.castShadow = true;
        object.receiveShadow = true;
        scene.add(object);
        splineHelperObjects.push(object);
        return object;
      }
      function addPoint() {
        splinePointsLength ++;
        positions.push(addSplineObject().position);
        updateSplineOutline();
      }
      function removePoint() {
        if (splinePointsLength <= 4) {
          return;
        }
        splinePointsLength --;
        positions.pop();
        scene.remove(splineHelperObjects.pop());
        updateSplineOutline();
      }
      function updateSplineOutline() {
        for (var k in splines) {
          var spline = splines[k];
          var splineMesh = spline.mesh;
          var position = splineMesh.geometry.attributes.position;
          for (var i = 0; i < ARC_SEGMENTS; i++) {
            var t = i / (ARC_SEGMENTS - 1);
            spline.getPoint(t, point);
            position.setXYZ(i, point.x, point.y, point.z);
          }
          position.needsUpdate = true;
        }
      }
      function exportSpline() {
        var strplace = [];
        for (var i = 0; i < splinePointsLength; i++) {
          var p = splineHelperObjects[i].position;
          strplace.push('new v3d.Vector3({0}, {1}, {2})'.format(p.x, p.y, p.z));
        }
        console.log(strplace.join(',\n'));
        var code = '[' + (strplace.join(',\n\t')) + ']';
        prompt('copy and paste code', code);
      }
      function load(new_positions) {
        while (new_positions.length > positions.length) {
          addPoint();
        }
        while (new_positions.length < positions.length) {
          removePoint();
        }
        for (var i = 0; i < positions.length; i++) {
          positions[i].copy(new_positions[i]);
        }
        updateSplineOutline();
      }
      function animate() {
        requestAnimationFrame(animate);
        render();
        stats.update();
      }
      function render() {
        splines.uniform.mesh.visible = params.uniform;
        splines.centripetal.mesh.visible = params.centripetal;
        splines.chordal.mesh.visible = params.chordal;
        renderer.render(scene, camera);
      }
    </script>

  </body>
</html>