Source: primitives/pointset.js

const THREE = require('three');
const Points = require('../three/Points').Points;
const toBufferGeometry = require('../utilities').toBufferGeometry;
const getCircularTexture = require('../utilities').getCircularTexture;
const Label = require('./label').Label;

/**
 * Provides an object which stores points and provides method which controls its position.
 * This is created when a valid json file containing point is read into a {@link Zinc.Scene}
 * object.
 * 
 * @class
 * @author Alan Wu
 * @return {Pointset}
 */
const Pointset = function () {
  (require('./zincObject').ZincObject).call(this);
  this.isPointset = true;
  const labelSets = [];

  /**
   * Create the pointsets using geometry and material.
   * 
   * @param {THREE.Geomtry} geometryIn - Geometry of points 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.createMesh = (geometryIn, materialIn, options) => {
    if (geometryIn && materialIn) {
      let geometry = toBufferGeometry(geometryIn, options);
      const texture = getCircularTexture();
      materialIn.map = texture;
      let point = new Points(geometry, materialIn);
      this.setMesh(point, options.localTimeEnabled, 
        options.localMorphColour);
    }
  }

  const addLabel = (index, coord, labelText, colourHex) => {
    if (labelText) {
      const colour = new THREE.Color(colourHex);
      const label = new Label(labelText, colour);
      label.setPosition(coord[0], coord[1], coord[2]);
      const sprite  = label.getSprite();
      sprite.material.sizeAttenuation = false;
      sprite.material.alphaTest = 0.5;
      sprite.material.transparent = true;
      sprite.material.depthWrite = false;
      sprite.material.depthTest = false;
      this.group.add(sprite);
      labelSets[index] = label;
    }
  }

  /**
   * Add points to existing mesh if it exists, otherwise
   * create a new one and add to it.
   * @param {Array} coords  -An array of three components coordinates.
   * @param {Array|String} labels - An array of strings, these are only added
   * if the number of coords equals to the number labels provided.
   * @param {Number} colour - A hex value of the colour for the points
   */
  this.addPoints = (coords, labels, colour) => {
    if (coords && coords.length > 0) {
      let current = this.drawRange - 1;
      const geometry = this.addVertices(coords);
      let mesh = this.getMorph();
      if (!mesh) {
        let material = new THREE.PointsMaterial({ alphaTest: 0.5, size: 10,
          color: colour, sizeAttenuation: false });
        const options = { localTimeEnabled: false, localMorphColour: false};
        geometry.colorsNeedUpdate = true;
        this.createMesh(geometry, material, options);
      }
      let end = current + coords.length;
      let index = 0;
      if ((Array.isArray(labels) && labels.length === coords.length) || 
        (typeof labels === "string")) {
        for (current; current + index < end;) {
          const labelText = typeof labels === "string" ? labels : labels[index];
          addLabel(index, coords[index], labelText, colour);
          index++;
        }
      }
      if (this.region) this.region.pickableUpdateRequired = true;
    }
  }

  /**
   * Set the size of the points.
   * 
   * @param {Number} size - size to be set.
   */
  this.setSize = size => {
    if (this.morph && this.morph.material) {
      this.morph.material.size = size;
      this.morph.material.needsUpdate = true;
    }
  }

  /**
   * Turn size attenuation on/off based on the flag.
   * 
   * @param {Boolean} flag - Determin either size attenuation
   * should be on or off.
   */
  this.setSizeAttenuation = flag => {
    if (this.morph && this.morph.material) {
      this.morph.material.sizeAttenuation = flag;
      this.morph.material.needsUpdate = true;
    }
  }

  /**
   * Get vertices at index
   */
  this.getVerticesByIndex = function(index) {
    if (index >= 0 && this.drawRange > index) {
      const positionAttribute = this.getMorph().geometry.getAttribute( 'position' );
      return [ 
        positionAttribute.getX(index),
        positionAttribute.getY(index),
        positionAttribute.getZ(index)
      ];
    }
    return undefined;
  }

  /**
   * Edit Vertice in index.
   */
  this.editVertices = function(coords, i) {
    if (coords && coords.length) {
      let mesh = this.getMorph();
      const maxIndex = i + coords.length - 1;
      if (!mesh || 0 > i || maxIndex >= this.drawRange) {
        return;
      } else {
        const positionAttribute = mesh.geometry.getAttribute( 'position' );
        let index = i;
        coords.forEach(coord => {
          const label = labelSets[index];
          if (label) {
            label.setPosition(coord[0], coord[1], coord[2]);
          }
          positionAttribute.setXYZ(index++, coord[0], coord[1], coord[2]);
          
        });
        positionAttribute.needsUpdate = true;
        this.boundingBoxUpdateRequired = true;
      }
    }
  }

  /**
 * Turn size attenuation on/off based on the flag.
 * 
 * @param {Boolean} flag - Determin either size attenuation
 * should be on or off.
 */
  this.render = (delta, playAnimation, cameraControls, options) => {
    if (this.morph && cameraControls) {
      this.morph.sizePerPixel = cameraControls.pixelHeight;
    }
    Pointset.prototype.render.call(this, delta, playAnimation, cameraControls, options);
  }
}

Pointset.prototype = Object.create((require('./zincObject').ZincObject).prototype);
exports.Pointset = Pointset;