Source: primitives/lines2.js

import * as THREE from 'three/webgpu';
import { Lines } from './lines';
import { LineSegments2 } from '../three/line/LineSegments2';
//import { LineMaterial } from '../three/line/LineMaterial';
import { LineSegmentsGeometry } from '../three/line/LineSegmentsGeometry';

/**
 * Provides an object which stores lines.
 * This is created when a valid json file containing lines is read into a {@link Zinc.Scene}
 * object.
 *
 * @class
 * @author Alan Wu
 * @return {Lines}
 */
const Lines2 = function () {
  Lines.call(this);
  this.isLines2 = true;
  //Shared with the GPU buffer of the mesh, edits are made in place
  let positions = new Float32Array(300);

  //Grow the positions array if required, returns true if it has
  //been reallocated.
  const ensureCapacity = (length) => {
    if (length > positions.length) {
      //Each segment has two vertices, six values
      const capacity = Math.ceil(Math.max(positions.length * 2, length) / 6) * 6;
      const newPositions = new Float32Array(capacity);
      newPositions.set(positions);
      positions = newPositions;
      return true;
    }
    return false;
  };

  //Fill the unused part of the array with degenerate segments at coord
  const fillUnused = (start, coord) => {
    for (let index = start; index + 2 < positions.length; index += 3) {
      positions[index] = coord[0];
      positions[index + 1] = coord[1];
      positions[index + 2] = coord[2];
    }
  };

  //Update the line distances without allocating new buffers
  const updateLineDistances = (mesh) => {
    const geometry = mesh.geometry;
    const distanceStart = geometry.attributes.instanceDistanceStart;
    const instanceStart = geometry.attributes.instanceStart;
    if (!distanceStart || distanceStart.data.array.length !== instanceStart.count * 2) {
      mesh.computeLineDistances();
      return;
    }
    const distances = distanceStart.data.array;
    let total = 0;
    for (let i = 0; i < instanceStart.count; i++) {
      const offset = i * 6;
      const dx = positions[offset + 3] - positions[offset];
      const dy = positions[offset + 4] - positions[offset + 1];
      const dz = positions[offset + 5] - positions[offset + 2];
      distances[i * 2] = total;
      total += Math.sqrt(dx * dx + dy * dy + dz * dz);
      distances[i * 2 + 1] = total;
    }
    distanceStart.data.needsUpdate = true;
  };

  //Upload the positions after they have been changed
  const updatePositions = (mesh, reallocated) => {
    const instanceStart = mesh.geometry.getAttribute('instanceStart');
    if (reallocated || !instanceStart || instanceStart.data.array !== positions) {
      //Only happens when the capacity has been increased
      mesh.geometry.setPositions(positions);
      mesh.computeLineDistances();
    } else {
      instanceStart.data.needsUpdate = true;
      updateLineDistances(mesh);
      mesh.geometry.computeBoundingBox();
      mesh.geometry.computeBoundingSphere();
    }
    this.boundingBoxUpdateRequired = true;
  };

  /**
   * Create the line segements using geometry and material.
   *
   * @param {Array} arrayIn - Geometry of lines to be rendered.
   * @param {THREE.Material} materialIn - Material to be set for the lines.
   * @param {Object} options - Provide various options
   * @param {Boolean} options.localTimeEnabled - A flag to indicate either the lines is
   * time dependent.
   * @param {Boolean} options.localMorphColour - A flag to indicate either the colour is
   * time dependent.
   */
  this.createLineSegment = (arrayIn, materialIn, options) => {
    if (arrayIn && materialIn) {
      const linesGeometry = new LineSegmentsGeometry();
      linesGeometry.setPositions(arrayIn);
      linesGeometry.colorsNeedUpdate = true;
      const line = new LineSegments2(linesGeometry, materialIn);
      line.scale.set(1, 1, 1);
      line.computeLineDistances();
      this.setMesh(line, options.localTimeEnabled, options.localMorphColour);
    }
  };

  /**
   * Set the width for the lines.
   *
   * @param {Number} width - Width of the lines.
   */
  this.setWidth = (width) => {
    this.requestRender();
    if (this.morph && this.morph.material) {
      this.morph.material.linewidth = width;
      this.morph.material.needsUpdate = true;
    }
  };

  /**
   * Add new vertices into the array
   */
  this.addVertices = function (coords) {
    this.requestRender();
    if (coords && coords.length) {
      let mesh = this.getMorph();
      if (!mesh) {
        this.drawRange = 0;
      }
      let index = this.drawRange * 3;
      const reallocated = ensureCapacity(index + coords.length * 3);
      coords.forEach((coord) => {
        positions[index++] = coord[0];
        positions[index++] = coord[1];
        positions[index++] = coord[2];
        this.drawRange++;
      });
      //fill the rest of the array.
      if (!mesh) {
        fillUnused(index, coords[0]);
      } else {
        if (reallocated) {
          fillUnused(index, coords[0]);
        }
        updatePositions(mesh, reallocated);
      }
    }
    return positions;
  };

  /**
   * Get the vertices by face index
   */
  this.getVerticesByFaceIndex = function (faceIndex) {
    let vIndex = faceIndex * 2 * 3;
    const mesh = this.getMorph();
    if (mesh && this.drawRange * 3 > vIndex) {
      const position = mesh.geometry.getAttribute('instanceStart');
      return [
        [position.data.array[vIndex], position.data.array[++vIndex], position.data.array[++vIndex]],
        [
          position.data.array[++vIndex],
          position.data.array[++vIndex],
          position.data.array[++vIndex],
        ],
      ];
    }
    return [];
  };

  /**
   * Edit Vertice in index.
   */
  this.editVertices = function (coords, i) {
    this.requestRender();
    if (coords && coords.length) {
      let mesh = this.getMorph();
      const maxIndex = i + coords.length - 1;
      if (!mesh || 0 > i || maxIndex >= this.drawRange) {
        return;
      } else {
        let index = i * 3;
        coords.forEach((coord) => {
          positions[index++] = coord[0];
          positions[index++] = coord[1];
          positions[index++] = coord[2];
        });
        fillUnused(this.drawRange * 3, coords[0]);
        updatePositions(mesh, false);
      }
    }
    return positions;
  };

  /**
   * Add new lines to existing lines if it exists, otherwise
   * create a new one and add to it.
   * @param {Array} coords  -An array of three components coordinates.

   * @param {Number} colour - A hex value of the colour for the points
   */
  this.addLines = (coords, colour) => {
    this.requestRender();
    if (coords && coords.length > 0) {
      this.addVertices(coords);
      let mesh = this.getMorph();
      if (!mesh) {
        const material = new THREE.Line2NodeMaterial({
          color: colour,
          linewidth: 1,
          vertexColors: false,
          worldUnits: false,
        });
        const options = { localTimeEnabled: false, localMorphColour: false };
        this.createLineSegment(positions, material, options);
      }
      if (this.region) this.region.pickableUpdateRequired = true;
    }
  };
};

Lines2.prototype = Object.create(Lines.prototype);
Lines2.prototype.constructor = Lines2;
export { Lines2 };