examples/editor_draw/editor_draw.js
function setupEditor() {
  /**
   *
   */
  function distanceBetween(point1, point2) {
    return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
  }

  /**
   *
   */
  function angleBetween(point1, point2) {
    return Math.atan2(point2.x - point1.x, point2.y - point1.y);
  }

  /**
   *
   */
  function initEditorStats() {
    let nbVoxels = stack2._columns * stack2._rows * stack2._frame.length;
    let textureSize = 4096;
    let textureDimension = textureSize * textureSize;

    for (let i = 0; i < nbVoxels; i++) {
      let rawDataIndex = ~~(i / textureDimension);
      let inRawDataIndex = i % textureDimension;
      let value = stack2.rawData[rawDataIndex][inRawDataIndex];
      editorStats[value] += 1;
    }

    updateEditorStatsDom();
  }

  /**
   *
   */
  function updateEditorStatsDom() {
    for (let i = 0; i < 3; i++) {
      document.getElementById(`editorSegment${i}Label`).innerHTML = segmentsList[i];
      document.getElementById(`editorSegment${i}Value`).innerHTML = editorStats[i];
    }
  }

  /**
   *  Loop through IJK BBox and see if voxel can be mapped to screen
   */
  function mapCanvasToData() {
    for (let i = ijkBBox[0]; i < ijkBBox[1] + 1; i++) {
      for (let j = ijkBBox[2]; j < ijkBBox[3] + 1; j++) {
        for (let k = ijkBBox[4]; k < ijkBBox[5] + 1; k++) {
          // ijk to world
          // center of voxel
          let worldCoordinate = new THREE.Vector3(i, j, k).applyMatrix4(stack2._ijk2LPS);
          // world to screen coordinate
          let screenCoordinates = worldCoordinate.clone();
          screenCoordinates.project(camera);

          screenCoordinates.x = Math.round(((screenCoordinates.x + 1) * canvas.offsetWidth) / 2);
          screenCoordinates.y = Math.round(((-screenCoordinates.y + 1) * canvas.offsetHeight) / 2);
          screenCoordinates.z = 0;

          let pixel = context.getImageData(screenCoordinates.x, screenCoordinates.y, 1, 1).data;
          if (pixel[3] > 0 && i >= 0 && j >= 0 && k >= 0) {
            // find index and texture
            let voxelIndex = i + j * stack2._columns + k * stack2._rows * stack2._columns;

            let textureSize = 4096;
            let textureDimension = textureSize * textureSize;

            let rawDataIndex = ~~(voxelIndex / textureDimension);
            let inRawDataIndex = voxelIndex % textureDimension;

            // update value...
            let oldValue = stack2.rawData[rawDataIndex][inRawDataIndex];
            let newValue = cursor.value;

            if (oldValue != newValue) {
              // update raw data
              stack2.rawData[rawDataIndex][inRawDataIndex] = newValue;

              // update texture that is passed to shader
              textures2[rawDataIndex].image.data = stack2.rawData[rawDataIndex]; // tex;
              textures2[rawDataIndex].needsUpdate = true;

              // update stats
              editorStats[oldValue] -= 1;
              editorStats[newValue] += 1;
            }
          }
        }
      }
    }
  }

  /**
   *
   */
  function drawCircle(x, y) {
    context.beginPath();
    context.arc(x, y, cursor.size, false, Math.PI * 2, false);
    context.closePath();
    context.fill();
    context.stroke();
  }

  /**
   *
   */
  function clearCanvas() {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  }

  /**
   *
   */
  function addEventListeners() {
    /**
     *
     */
    function onMouseDown(e) {
      if (!isEditing) return;

      isDrawing = true;
      lastPoint = {
        x: e.pageX - canvasDiv.offsetLeft,
        y: e.pageY - canvasDiv.offsetTop,
      };
    }

    /**
     *
     */
    function onMouseMove(e) {
      if (!isEditing) return;

      currentPoint = {
        x: e.pageX - canvasDiv.offsetLeft,
        y: e.pageY - canvasDiv.offsetTop,
      };

      context.strokeStyle = cursor.color;
      context.globalCompositeOperation = 'xor';
      context.globalAlpha = 0.5;
      context.fillStyle = cursor.color;

      if (isDrawing) {
        let dist = distanceBetween(lastPoint, currentPoint);
        let angle = angleBetween(lastPoint, currentPoint);

        for (let i = 0; i < dist; i += 5) {
          let x = lastPoint.x + Math.sin(angle) * i;
          let y = lastPoint.y + Math.cos(angle) * i;
          drawCircle(x, y);
        }

        lastPoint = currentPoint;
      } else {
        clearCanvas();
      }

      // draw under the cursor
      context.globalCompositeOperation = 'source-over';
      context.globalAlpha = 1;
      context.fillStyle = 'rgba(0, 0, 0, 0)';
      drawCircle(currentPoint.x, currentPoint.y);
    }

    /**
     *
     */
    function onMouseUp(e) {
      if (!isEditing) return;

      isDrawing = false;
      mapCanvasToData();
      clearCanvas();
      updateEditorStatsDom();
      // draw cursor under mouse
      onMouseMove(e);
    }

    /**
     *
     */
    function updateDOM() {
      // lets events go through or not for scrolling, padding, zooming, etc.
      if (isEditing) {
        canvasDiv.className = 'editing';
        document.getElementById('help').style.display = 'none';
      } else {
        canvasDiv.className = 'exploring';
        document.getElementById('help').style.display = 'block';
      }
    }

    /**
     *
     */
    function onKeyDown(e) {
      if (e.keyCode === 17) {
        isEditing = true;
        isDrawing = false;
        updateDOM();
      }
    }

    /**
     *
     */
    function onKeyUp(e) {
      if (e.keyCode === 17) {
        isEditing = false;
        isDrawing = false;
        clearCanvas();
        updateDOM();
      }
    }

    /**
     *
     */
    function disableRightClick(e) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }

    // add events listeners
    canvasDiv.addEventListener('mousedown', onMouseDown, false);
    canvasDiv.addEventListener('mousemove', onMouseMove, false);
    canvasDiv.addEventListener('mouseup', onMouseUp, false);
    window.addEventListener('keydown', onKeyDown, false);
    window.addEventListener('keyup', onKeyUp, false);
    canvasDiv.addEventListener('contextmenu', disableRightClick, false);
  }

  addEventListeners();
  initEditorStats();
}
function setupEditor() {
  /**
   *
   */
  function distanceBetween(point1, point2) {
    return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
  }

  /**
   *
   */
  function angleBetween(point1, point2) {
    return Math.atan2(point2.x - point1.x, point2.y - point1.y);
  }

  /**
   *
   */
  function initEditorStats() {
    let nbVoxels = stack2._columns * stack2._rows * stack2._frame.length;
    let textureSize = 4096;
    let textureDimension = textureSize * textureSize;

    for (let i = 0; i < nbVoxels; i++) {
      let rawDataIndex = ~~(i / textureDimension);
      let inRawDataIndex = i % textureDimension;
      let value = stack2.rawData[rawDataIndex][inRawDataIndex];
      editorStats[value] += 1;
    }

    updateEditorStatsDom();
  }

  /**
   *
   */
  function updateEditorStatsDom() {
    for (let i = 0; i < 3; i++) {
      document.getElementById(`editorSegment${i}Label`).innerHTML = segmentsList[i];
      document.getElementById(`editorSegment${i}Value`).innerHTML = editorStats[i];
    }
  }

  /**
   *  Loop through IJK BBox and see if voxel can be mapped to screen
   */
  function mapCanvasToData() {
    for (let i = ijkBBox[0]; i < ijkBBox[1] + 1; i++) {
      for (let j = ijkBBox[2]; j < ijkBBox[3] + 1; j++) {
        for (let k = ijkBBox[4]; k < ijkBBox[5] + 1; k++) {
          // ijk to world
          // center of voxel
          let worldCoordinate = new THREE.Vector3(i, j, k).applyMatrix4(stack2._ijk2LPS);
          // world to screen coordinate
          let screenCoordinates = worldCoordinate.clone();
          screenCoordinates.project(camera);

          screenCoordinates.x = Math.round(((screenCoordinates.x + 1) * canvas.offsetWidth) / 2);
          screenCoordinates.y = Math.round(((-screenCoordinates.y + 1) * canvas.offsetHeight) / 2);
          screenCoordinates.z = 0;

          let pixel = context.getImageData(screenCoordinates.x, screenCoordinates.y, 1, 1).data;
          if (pixel[3] > 0 && i >= 0 && j >= 0 && k >= 0) {
            // find index and texture
            let voxelIndex = i + j * stack2._columns + k * stack2._rows * stack2._columns;

            let textureSize = 4096;
            let textureDimension = textureSize * textureSize;

            let rawDataIndex = ~~(voxelIndex / textureDimension);
            let inRawDataIndex = voxelIndex % textureDimension;

            // update value...
            let oldValue = stack2.rawData[rawDataIndex][inRawDataIndex];
            let newValue = cursor.value;

            if (oldValue != newValue) {
              // update raw data
              stack2.rawData[rawDataIndex][inRawDataIndex] = newValue;

              // update texture that is passed to shader
              textures2[rawDataIndex].image.data = stack2.rawData[rawDataIndex]; // tex;
              textures2[rawDataIndex].needsUpdate = true;

              // update stats
              editorStats[oldValue] -= 1;
              editorStats[newValue] += 1;
            }
          }
        }
      }
    }
  }

  /**
   *
   */
  function drawCircle(x, y) {
    context.beginPath();
    context.arc(x, y, cursor.size, false, Math.PI * 2, false);
    context.closePath();
    context.fill();
    context.stroke();
  }

  /**
   *
   */
  function clearCanvas() {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  }

  /**
   *
   */
  function addEventListeners() {
    /**
     *
     */
    function onMouseDown(e) {
      if (!isEditing) return;

      isDrawing = true;
      lastPoint = {
        x: e.pageX - canvasDiv.offsetLeft,
        y: e.pageY - canvasDiv.offsetTop,
      };
    }

    /**
     *
     */
    function onMouseMove(e) {
      if (!isEditing) return;

      currentPoint = {
        x: e.pageX - canvasDiv.offsetLeft,
        y: e.pageY - canvasDiv.offsetTop,
      };

      context.strokeStyle = cursor.color;
      context.globalCompositeOperation = 'xor';
      context.globalAlpha = 0.5;
      context.fillStyle = cursor.color;

      if (isDrawing) {
        let dist = distanceBetween(lastPoint, currentPoint);
        let angle = angleBetween(lastPoint, currentPoint);

        for (let i = 0; i < dist; i += 5) {
          let x = lastPoint.x + Math.sin(angle) * i;
          let y = lastPoint.y + Math.cos(angle) * i;
          drawCircle(x, y);
        }

        lastPoint = currentPoint;
      } else {
        clearCanvas();
      }

      // draw under the cursor
      context.globalCompositeOperation = 'source-over';
      context.globalAlpha = 1;
      context.fillStyle = 'rgba(0, 0, 0, 0)';
      drawCircle(currentPoint.x, currentPoint.y);
    }

    /**
     *
     */
    function onMouseUp(e) {
      if (!isEditing) return;

      isDrawing = false;
      mapCanvasToData();
      clearCanvas();
      updateEditorStatsDom();
      // draw cursor under mouse
      onMouseMove(e);
    }

    /**
     *
     */
    function updateDOM() {
      // lets events go through or not for scrolling, padding, zooming, etc.
      if (isEditing) {
        canvasDiv.className = 'editing';
        document.getElementById('help').style.display = 'none';
      } else {
        canvasDiv.className = 'exploring';
        document.getElementById('help').style.display = 'block';
      }
    }

    /**
     *
     */
    function onKeyDown(e) {
      if (e.keyCode === 17) {
        isEditing = true;
        isDrawing = false;
        updateDOM();
      }
    }

    /**
     *
     */
    function onKeyUp(e) {
      if (e.keyCode === 17) {
        isEditing = false;
        isDrawing = false;
        clearCanvas();
        updateDOM();
      }
    }

    /**
     *
     */
    function disableRightClick(e) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }

    // add events listeners
    canvasDiv.addEventListener('mousedown', onMouseDown, false);
    canvasDiv.addEventListener('mousemove', onMouseMove, false);
    canvasDiv.addEventListener('mouseup', onMouseUp, false);
    window.addEventListener('keydown', onKeyDown, false);
    window.addEventListener('keyup', onKeyUp, false);
    canvasDiv.addEventListener('contextmenu', disableRightClick, false);
  }

  addEventListeners();
  initEditorStats();
}
FUNCTIONS