Source: primitives/textureSlides.js

const THREE = require('three');
const shader = require("../shaders/textureSlide.js");
/**
 * Provides a class which create a texture stacks in a block
 * with shaders allowing slices of texture to be displayed.
 * 
 * @param {TextureArray} textureIn - An object of texture array
 * holding texture information.
 * 
 * @class
 * @author Alan Wu
 * @return {TextureSlides}
 */
const TextureSlides = function (textureIn) {
  (require('./texturePrimitive').TexturePrimitive).call(this, textureIn);
  this.isTextureSlides = true;
  const textureSettings = [];
  const idTextureMap = {};
  this.morph = new THREE.Group();
  this.group = this.morph;
  this.morph.userData = this;
  let flipY = true;

  /**
    @typedef SLIDE_SETTINGS
    @type {Set}
    @property {String} direction - the value must be x, y or z, specify the
    direction the slide should be facing.
    @property {Number} value - Normalised value of the location on direction.
    @property {String} id - ID of the mesh, it is only available if the settings
    is returned from {@link TextureSlides.createSlide} or 
    {@link TextureSlides.getTextureSettings}.
   */
  /**
   * Create the slides required for visualisation based on the slide settings.
   * The slides themselves are {THREE.PlanGeometry} objects.
   * 
   * @param {SLIDE_SETTINGS} slideSettings - An array to each slide settings.
   */
  this.createSlides = slideSettings => {
    slideSettings.forEach(slide => this.createSlide(slide));
  }

  /**
   * Set the value of the uniforms for a specific mesh in this
   * texture slide object.
   *
   * @param {THREE.Mesh} mesh - Mesh to be modified
   * @param {SLIDE_SETTINGS} slideSettings - Slide settings.
   */
  const setUniformSlideSettingsOfMesh = (mesh, settings) => {
    const material = mesh.material;
    const uniforms = material.uniforms;
    mesh.rotation.x = 0;
    mesh.rotation.y = 0;
    mesh.rotation.z = 0;
    mesh.position.x = 0;
    mesh.position.y = 0;
    mesh.position.z = 0;
    switch (settings.direction) {
      case "x":
        const rotation = flipY ? -Math.PI / 2 : Math.PI / 2;
        mesh.rotation.y = rotation;
        uniforms.direction.value = 1;
        uniforms.slide.value.set(settings.value, 0, 0);
        mesh.position.x = settings.value;
        break;
      case "y":
        mesh.rotation.x = Math.PI / 2;
        uniforms.direction.value = 2;
        uniforms.slide.value.set(0, settings.value, 0);
        mesh.position.y = settings.value;
        break;
      case "z":
        uniforms.direction.value = 3;
        uniforms.slide.value.set(0, 0, settings.value);
        mesh.position.z = settings.value;
        break;
      default:
        break;
    }
    material.needsUpdate = true;
    this.boundingBoxUpdateRequired = true;
  }

  /**
   * Modify the mesh based on a setting
   *
   * @param {SLIDE_SETTINGS} settings - s.
   */
  this.modifySlideSettings = (settings) => {
    if (settings && settings.id &&
      settings.id in idTextureMap &&
      idTextureMap[settings.id]) {
      setUniformSlideSettingsOfMesh(idTextureMap[settings.id], settings);
    }
  }

  /**
   * Create a slide required for visualisation based on the slide settings.
   * The slide itself is an {THREE.PlanGeometry} object.
   *
   * @param {SLIDE_SETTINGS} settings -settings of the slide to be created.
   * @return {SLIDE_SETTINGS} - Returned settings, it includes the newly
   * created mesh's id.
   */
  this.createSlide = settings => {
    if (this.texture && this.texture.isTextureArray && this.texture.isReady()) {
      if (settings && settings.direction && settings.value !== undefined) {
        const geometry = new THREE.PlaneGeometry(1, 1);
        geometry.translate(0.5, 0.5, 0);
        const uniforms = shader.getUniforms();
        uniforms.diffuse.value = this.texture.impl;
        uniforms.depth.value = this.texture.size.depth;
        uniforms.flipY.value = flipY;
        
        const options = {
          fs: shader.fs,
          vs: shader.vs,
          uniforms: uniforms,
          glslVersion: shader.glslVersion,
          side: THREE.DoubleSide,
          transparent: false
        };
        const material = this.texture.getMaterial(options);
        material.needsUpdate = true;
        const mesh = new THREE.Mesh(geometry, material);
        mesh.name = this.groupName;
        mesh.userData = this;
        const slideSettings = {
          value: settings.value,
          direction: settings.direction,
          id: mesh.id,
        };
        textureSettings.push(slideSettings);
        setUniformSlideSettingsOfMesh(mesh, slideSettings);
        idTextureMap[mesh.id] = mesh;
        this.morph.add(mesh);
        this.boundingBoxUpdateRequired = true;
        return slideSettings;
      }
    }
  }

  /**
   * Return a copy of texture settings used by this object.
   *
   * @return {SLIDE_SETTINGS} - Returned the list of settings.
   */
  this.getTextureSettings = () => {
    return [...textureSettings];
  }

  /**
   * Return a copy of texture settings with corresponding id used by this object.
   *
   * @return {SLIDE_SETTINGS} - Returned a copy of settings with corresponding id.
   */
  this.getTextureSettingsWithId = (id) => {
    for (let i = 0; i < textureSettings.length; i++) {
      if (id === textureSettings[i].id) {
        return {...textureSettings[i]};
      }
    }
  }

  /**
   * Get  the array of slides, return them in an array
   *
   * @return {Array} - Return an array of {@link THREE.Object)
   */
  this.getSlides = () => {
    if (this.morph) return [...this.morph.children];
    return [];
  }

  /**
   * Remove a slide, this will dispose the slide and its material.
   *
   * @param {Slide} slide - Slide to be remvoed
   */
  this.removeSlide = slide => {
    if (slide) {
      this.removeSlideWithId(slide.id);
    }
  }

  /**
    * Remove a slide, this will dispose the slide and its material.
    *
    * @param {Number} id - id of slide to be remvoed
    */
  this.removeSlideWithId = id => {
    if (this.morph && id in idTextureMap && idTextureMap[id]) {
      if (this.morph.getObjectById(id)) {
        const slide = idTextureMap[id];
        this.morph.remove(slide);
        slide.clear();
        if (slide.geometry)
          slide.geometry.dispose();
        if (slide.material)
          slide.material.dispose();
        this.boundingBoxUpdateRequired = true;
      }
      const index = textureSettings.findIndex(item => item.id === id);
      if (index > -1) {
        textureSettings.splice(index, 1);
      }
    }
  }

  /**
   * Clean up all internal objects.
   */
  this.dispose = () => {
    this.morph.children.forEach(slide => {
      if (slide.geometry)
        slide.geometry.dispose();
      if (slide.material)
        slide.material.dispose();
    });
    (require('./texturePrimitive').TexturePrimitive).prototype.dispose.call(this);
    this.boundingBoxUpdateRequired = true;
  }

  //Expand the boundingbox with slide settings
  const expandBoxWithSettings = (box, settings, vector) => {
    switch (settings.direction.value) {
      case 1:
        vector.copy(settings.slide.value);
        box.expandByPoint(vector);
        vector.setY(1.0);
        vector.setZ(1.0);
        box.expandByPoint(vector);
        break;
      case 2:
        vector.copy(settings.slide.value);
        box.expandByPoint(vector);
        vector.setX(1.0);
        vector.setZ(1.0);
        box.expandByPoint(vector);
        break;
      case 3:
        vector.copy(settings.slide.value);
        box.expandByPoint(vector);
        vector.setX(1.0);
        vector.setY(1.0);
        box.expandByPoint(vector);
        break;
      default:
        break;
    }
  }

  /**
   * Get the bounding box of this slides.
   * It uses the max and min of the slides position and the
   * transformation to calculate the position of the box.
   * 
   * @return {THREE.Box3}.
   */
  this.getBoundingBox = () => {
    if (this.morph && this.morph.children && this.morph.visible &&
      this.boundingBoxUpdateRequired) {
      this.cachedBoundingBox.makeEmpty();
      const vector = new THREE.Vector3(0, 0, 0);
      this.morph.children.forEach(slide => {
        expandBoxWithSettings(this.cachedBoundingBox, slide.material.uniforms,
          vector);
      });
      this.morph.updateMatrixWorld (true, true);
      this.cachedBoundingBox.applyMatrix4(this.morph.matrixWorld);
      this.boundingBoxUpdateRequired = false;
    }
    return this.cachedBoundingBox;
  }

  this.applyTransformation = (rotation, position, scale) => {
    const matrix = new THREE.Matrix4();
    matrix.set(
      rotation[0],
      rotation[1],
      rotation[2],
      0,
      rotation[3],
      rotation[4],
      rotation[5],
      0,
      rotation[6],
      rotation[7],
      rotation[8],
      0,
      0,
      0,
      0,
      0
    );
    const quaternion = new THREE.Quaternion().setFromRotationMatrix(matrix);
    this.morph.position.set(...position);
    this.morph.quaternion.copy( quaternion );
    this.morph.scale.set(...scale);
    this.morph.updateMatrix();
    this.boundingBoxUpdateRequired = true;
  }

  this.initialise = (textureData, finishCallback) => {
    if (textureData) {

      const locations = textureData.locations;
      if (locations && locations.length > 0) {
        this.applyTransformation(locations[0].orientation,
          locations[0].position, locations[0].scale);
        if ("flipY" in locations[0]) {
          flipY = locations[0].flipY;
        }
      }
      this.createSlides(textureData.settings.slides);
      if (finishCallback != undefined && (typeof finishCallback == 'function')) {
        finishCallback(this);
      }
    }
  }
}

TextureSlides.prototype = Object.create((require('./texturePrimitive').TexturePrimitive).prototype);
TextureSlides.prototype.constructor = TextureSlides;
exports.TextureSlides = TextureSlides;