import EventEmitter from 'eventemitter3'; import extend from 'lodash.assign'; import shortid from 'shortid'; import THREE from 'three'; import Scene from '../engine/Scene'; import {CSS3DObject} from '../vendor/CSS3DRenderer'; import {CSS2DObject} from '../vendor/CSS2DRenderer'; // TODO: Make sure nothing is left behind in the heap after calling destroy() // TODO: Need a single move method that handles moving all the various object // layers so that the DOM layers stay in sync with the 3D layer // TODO: Double check that objects within the _object3D Object3D parent are frustum // culled even if the layer position stays at the default (0,0,0) and the child // objects are positioned much further away // // Or does the layer being at (0,0,0) prevent the child objects from being // culled because the layer parent is effectively always in view even if the // child is actually out of camera class Layer extends EventEmitter { constructor(options) { super(); var defaults = { id: shortid.generate(), output: true, outputToScene: true }; this._options = extend({}, defaults, options); if (this.isOutput()) { this._object3D = new THREE.Object3D(); this._dom3D = document.createElement('div'); this._domObject3D = new CSS3DObject(this._dom3D); this._dom2D = document.createElement('div'); this._domObject2D = new CSS2DObject(this._dom2D); } } // Add THREE object directly to layer add(object) { this._object3D.add(object); } // Remove THREE object from to layer remove(object) { this._object3D.remove(object); } addDOM3D(object) { this._domObject3D.add(object); } removeDOM3D(object) { this._domObject3D.remove(object); } addDOM2D(object) { this._domObject2D.add(object); } removeDOM2D(object) { this._domObject2D.remove(object); } // Add layer to world instance and store world reference addTo(world) { return world.addLayer(this); } // Internal method called by World.addLayer to actually add the layer _addToWorld(world) { this._world = world; return new Promise((resolve, reject) => { this._onAdd(world).then(() => { this.emit('added'); resolve(this); }).catch(reject); }); } // Must return a promise _onAdd(world) { return Promise.resolve(this); } getPickingId() { if (this._world._engine._picking) { return this._world._engine._picking.getNextId(); } return false; } // TODO: Tidy this up and don't access so many private properties to work addToPicking(object) { if (!this._world._engine._picking) { return; } this._world._engine._picking.add(object); } removeFromPicking(object) { if (!this._world._engine._picking) { return; } this._world._engine._picking.remove(object); } isOutput() { return this._options.output; } isOutputToScene() { return this._options.outputToScene; } // TODO: Also hide any attached DOM layers hide() { this._object3D.visible = false; if (this._pickingMesh) { this._pickingMesh.visible = false; } } // TODO: Also show any attached DOM layers show() { this._object3D.visible = true; if (this._pickingMesh) { this._pickingMesh.visible = true; } } // Destroys the layer and removes it from the scene and memory destroy() { if (this._object3D && this._object3D.children) { // Remove everything else in the layer var child; for (var i = this._object3D.children.length - 1; i >= 0; i--) { child = this._object3D.children[i]; if (!child) { continue; } this.remove(child); if (child.geometry) { // Dispose of mesh and materials child.geometry.dispose(); child.geometry = null; } if (child.material) { if (child.material.map) { child.material.map.dispose(); child.material.map = null; } child.material.dispose(); child.material = null; } } } if (this._domObject3D && this._domObject3D.children) { // Remove everything else in the layer var child; for (var i = this._domObject3D.children.length - 1; i >= 0; i--) { child = this._domObject3D.children[i]; if (!child) { continue; } this.removeDOM3D(child); } } if (this._domObject2D && this._domObject2D.children) { // Remove everything else in the layer var child; for (var i = this._domObject2D.children.length - 1; i >= 0; i--) { child = this._domObject2D.children[i]; if (!child) { continue; } this.removeDOM2D(child); } } this._domObject3D = null; this._domObject2D = null; this._world = null; this._object3D = null; } } export default Layer; var noNew = function(options) { return new Layer(options); }; export {noNew as layer};