const THREE = require('three');
const updateMorphColorAttribute = require("../utilities").updateMorphColorAttribute;
const toBufferGeometry = require('../utilities').toBufferGeometry;
/**
* Provides an object which stores meshes at different levels based
* on specified distance.
* This object is ued by zincObject to provide mesh at different LODs.
* A layer is displayed when the distance from the camera is greater
* than its specified distance and closest compared to other layers.
* This is intended to be an internal object used only by Zinc Object.
*
* This object assumes the centroid and bounding box are consistent between
* different level of layers.
*
* @class
* @author Alan Wu
* @return {LOD}
*/
const LOD = function (parent) {
this.levels = [];
this._currentLevel = 0;
this._renderOrder = 1;
this._material = undefined;
this._secondaryMaterial = undefined;
this._loader = undefined;
//The owning Zinc Object
this._parent = parent;
/*
* Add a level of LOD at the specified distance
*/
this.addLevel = (object, distanceIn) => {
if (object) {
const distance = Math.abs(distanceIn);
let l;
for (l = 0; l < this.levels.length; l++) {
if (distance < this.levels[l].distance) {
break;
}
}
const levelObject = {
distance: distance,
morph: object,
loaded: true,
loading: false,
url: "",
};
this.levels.splice(l, 0, levelObject);
object.renderOrder = this._renderOrder;
//this.add( object );
}
}
/*
* This is called once an ondemand level is loaded
*/
this.levelLoaded = (object, distanceIn) => {
if (object) {
const distance = Math.abs(distanceIn);
for (let l = 0; l < this.levels.length; l++) {
if (distance === this.levels[l].distance) {
this._parent.group.add(object);
this.levels[l].morph = object;
this.levels[l].loaded = true;
this.levels[l].loading = false;
break;
}
}
this.checkTransparentMesh();
}
}
this.addLevelFromURL = (loader, level, url, preload) => {
this._loader = loader;
const distance = this.calculateDistance(level);
const levelObject = {
distance: distance,
morph: undefined,
loaded: false,
loading: false,
url: url,
};
let l;
for (l = 0; l < this.levels.length; l++) {
if (distance < this.levels[l].distance) {
break;
}
}
this.levels.splice(l, 0, levelObject);
if (preload) {
this.loadLevel(l);
}
}
//load the mesh at index, return true if morph is not ready
this.loadLevel = (index) => {
const level = this.levels[index];
if (!level.morph && !level.loaded &&
!level.loading) {
level.loading = true;
this._loader.load(level.url, this.lodLoader(level.distance),
undefined, undefined);
}
return (level.morph === undefined);
}
this.calculateDistance = function (level) {
this._parent.getBoundingBox();
const radius = this._parent.radius;
let distance = 0;
if (level === "far") {
distance = radius * 4.5;
} else if (level === "medium") {
distance = radius * 2.5;
} else if (level === "close") {
distance = 0;
}
return distance;
}
/**
* Check if there are multiple levels.
*/
this.containsLevels = () => {
if (this.levels && this.levels.length > 1) {
return true;
}
return false;
}
/**
* Check if material is transparent, create secondary mesh
* for better rendering if required.
*/
this.checkTransparentMesh = () => {
const level = this.levels[this._currentLevel];
if (this._material) {
if (this._material.transparent) {
if (!this._secondaryMaterial) {
this._secondaryMaterial = this._material.clone();
this._secondaryMaterial.side = THREE.FrontSide;
}
this._secondaryMaterial.opacity = this._material.opacity;
if (this._secondaryMaterial.emissive) {
this._secondaryMaterial.emissive.copy(this._material.emissive);
}
this._secondaryMaterial.needsUpdate = true;
// THREE.Mesh - for utilities purpose such as rendering
// transparent surfaces - one for front face and one for back face.
if (!level.secondaryMesh) {
level.secondaryMesh = new THREE.Mesh(level.morph.geometry,
this._secondaryMaterial);
level.secondaryMesh.renderOrder = level.morph.renderOrder + 1;
level.secondaryMesh.userData = level.morph.userData;
level.secondaryMesh.name = level.morph.name;
}
this._material.side = THREE.BackSide;
this._material.needsUpdate = true;
if (!level.secondaryMesh.parent) {
level.morph.add(level.secondaryMesh);
if (this._parent.animationGroup) {
this._parent.animationGroup.add(level.secondaryMesh);
}
}
} else {
if (level.secondaryMesh) {
//Do not delete this mesh, remove it from
//rendering and animation group instead
level.morph.remove(level.secondaryMesh);
if (this._parent.animationGroup) {
this._parent.animationGroup.uncache(level.secondaryMesh);
this._parent.animationGroup.remove(level.secondaryMesh);
}
}
this._material.side = THREE.DoubleSide;
this._material.needsUpdate = true;
}
}
}
this.dispose = () => {
this.levels.forEach((level) => {
if (level.morph && level.morph.geometry) {
level.morph.geometry.dispose();
}
});
if (this._material) {
this._material.dispose();
}
if (this._secondaryMaterial) {
this._secondaryMaterial.dispose();
}
}
this.getCurrentLevel = () => {
return this._currentLevel;
}
this.getCurrentMorph = () => {
const level = this.levels[this._currentLevel];
if (level && level.morph) {
return level.morph;
}
return this._parent.morph;
}
/**
* Loader for lod object
*/
this.lodLoader = function (distance) {
return (geometryIn) => {
const material = this._material;
const options = {
localTimeEnabled: this._parent.timeEnabled,
localMorphColour: this._parent.morphColour,
}
const geometry = toBufferGeometry(geometryIn, options);
let mesh = undefined;
if (this._parent.isGeometry) {
mesh = new THREE.Mesh(geometry, material);
} else if (this._parent.isLines) {
mesh = new (require("../three/line/LineSegments").LineSegments)(geometry, material);
}
mesh.userData = this._parent;
mesh.renderOrder = this._renderOrder;
geometryIn.dispose();
this.levelLoaded(mesh, distance);
};
}
this.updateMorphColorAttribute = (currentOnly) => {
//Multilayers - set all
if (this._material) {
if ((this._material.vertexColors == THREE.VertexColors) ||
(this._material.vertexColors == true)) {
if (currentOnly) {
const morph = this.getCurrentMorph();
updateMorphColorAttribute(morph.geometry, morph);
} else {
this.levels.forEach((level) => {
if (level.morph && level.morph.geometry) {
updateMorphColorAttribute(level.morph.geometry, level.morph);
}
});
}
}
}
}
this.setColour = (colour) => {
this._material.color = colour;
if (this._secondaryMaterial) {
this._secondaryMaterial.color = colour;
}
updateGeometryColour();
}
this.setFrustumCulled = (flag) => {
this.levels.forEach((level) => {
if (level.morph) {
level.morph.frustumCulled = flag;
}
if (level.secondaryMesh) {
level.secondaryMesh.frustumCulled = flag;
}
});
}
this.setMaterial = (material) => {
if (material) {
if (!this._material || (this._material.id !== material.id)) {
this._material = material;
if (this._secondaryMaterial) {
this._secondaryMaterial.dispose();
}
this._secondaryMaterial = material.clone()
this._secondaryMaterial.side = THREE.FrontSide;
this._secondaryMaterial.transparent = true;
this.levels.forEach((level) => {
if (level.morph) {
level.morph.material = this._material;
if (level.morph.geometry) {
level.morph.geometry.colorsNeedUpdate = true;
}
}
if (level.secondaryMesh) {
level.secondaryMesh.material = this._secondaryMaterial;
}
});
}
}
}
this.setName = (name) => {
this.levels.forEach((level) => {
if (level.morph) {
level.morph.name = name;
}
if (level.secondaryMesh) {
level.secondaryMesh.name = name;
}
});
}
this.setRenderOrder = (order) => {
this._renderOrder = order;
this.levels.forEach((level) => {
if (level.morph) {
level.morph.renderOrder = order;
}
if (level.secondaryMesh) {
level.secondaryMesh.renderOrder = order;
}
});
}
this.setVertexColors = (vertexColors) => {
this._material.vertexColors = vertexColors;
updateGeometryColour();
if (this._secondaryMaterial) {
this._secondaryMaterial.vertexColors = vertexColors;
}
}
/* Update layers based on the */
this.update = (camera, center) => {
const levels = this.levels;
if (levels.length > 1) {
const distance = camera.cameraObject.position.distanceTo(center);
let visibleIndex = -1;
let optimalIndex = -1;
let i, l;
//Found a visible index that is within range of the LOD
for (i = 0, l = levels.length; i < l; i++) {
if (distance >= levels[i].distance) {
//Check if a level is loading
if (levels[i].morph) {
if (visibleIndex > -1 && levels[visibleIndex].morph) {
levels[visibleIndex].morph.visible = false;
}
visibleIndex = i;
levels[i].morph.visible = true;
optimalIndex = -1;
}
else {
optimalIndex = i;
}
} else {
break;
}
}
if (optimalIndex > -1) {
this.loadLevel(optimalIndex);
}
for (; i < l; i++) {
if (levels[i].morph) {
//Set visibility of other morph to false
//and set the closest lod to true if
//none is found
if (visibleIndex > -1) {
levels[i].morph.visible = false;
} else {
levels[i].morph.visible = true;
visibleIndex = i;
}
}
}
if (this._currentLevel != visibleIndex) {
this._currentLevel = visibleIndex;
this.checkTransparentMesh();
}
}
}
this.toggleMarker = (marker, flag) => {
this.levels.forEach((level) => {
if (level.morph) {
if (flag) {
level.morph.add(marker);
} else {
level.morph.remove(marker);
}
}
});
}
const updateGeometryColour = () => {
this.levels.forEach((level) => {
if (level.morph && level.morph.geometry) {
level.morph.geometry.colorsNeedUpdate = true;
}
});
}
}
exports.LOD = LOD;