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;