diff --git a/src/Core/View.js b/src/Core/View.js index 569cf2878a..a08386cf7f 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -12,6 +12,7 @@ import { getMaxColorSamplerUnitsCount } from 'Renderer/LayeredMaterial'; import Scheduler from 'Core/Scheduler/Scheduler'; import Picking from 'Core/Picking'; import LabelLayer from 'Layer/LabelLayer'; +import ObjectRemovalHelper from 'Process/ObjectRemovalHelper'; export const VIEW_EVENTS = { /** @@ -271,35 +272,41 @@ class View extends THREE.EventDispatcher { * - remove all layers * - remove all frame requester * - remove all events + * @param {boolean} [clearCache=false] Whether to clear all the caches or not (layers cache, style cache, tilesCache) */ - dispose() { + dispose(clearCache = false) { const id = viewers.indexOf(this); if (id == -1) { console.warn('View already disposed'); return; } // controls dispose - if (this.controls && this.controls.dispose) { - this.controls.dispose(); + if (this.controls) { + if (typeof this.controls.dispose === 'function') { + this.controls.dispose(); + } + delete this.controls; } // remove alls frameRequester this.removeAllFrameRequesters(); // remove alls events this.removeAllEvents(); - // remove alls layers + // remove all layers const layers = this.getLayers(l => !l.isTiledGeometryLayer && !l.isAtmosphere); for (const layer of layers) { - this.removeLayer(layer.id); + this.removeLayer(layer.id, clearCache); } const atmospheres = this.getLayers(l => l.isAtmosphere); for (const atmosphere of atmospheres) { - this.removeLayer(atmosphere.id); + this.removeLayer(atmosphere.id, clearCache); } const tileLayers = this.getLayers(l => l.isTiledGeometryLayer); for (const tileLayer of tileLayers) { - this.removeLayer(tileLayer.id); + this.removeLayer(tileLayer.id, clearCache); } viewers.splice(id, 1); + // Remove remaining objects in the scene (e.g. helpers, debug, etc.) + this.scene.traverse(ObjectRemovalHelper.cleanup); } /** @@ -378,16 +385,17 @@ class View extends THREE.EventDispatcher { * Removes a specific imagery layer from the current layer list. This removes layers inserted with attach(). * @example * view.removeLayer('layerId'); - * @param {string} layerId The identifier - * @return {boolean} + * @param {string} layerId The identifier + * @param {boolean} [clearCache=false] Whether to clear all the layer cache or not + * @return {boolean} */ - removeLayer(layerId) { + removeLayer(layerId, clearCache) { const layer = this.getLayerById(layerId); if (layer) { const parentLayer = layer.parent; // Remove and dispose all nodes - layer.delete(); + layer.delete(clearCache); // Detach layer if it's attached if (parentLayer && !parentLayer.detach(layer)) { diff --git a/src/Layer/GeometryLayer.js b/src/Layer/GeometryLayer.js index d1766a9763..04595f5c54 100644 --- a/src/Layer/GeometryLayer.js +++ b/src/Layer/GeometryLayer.js @@ -1,32 +1,7 @@ import Layer from 'Layer/Layer'; import Picking from 'Core/Picking'; import { CACHE_POLICIES } from 'Core/Scheduler/Cache'; - -function disposeMesh(obj) { - if (obj.dispose) { - obj.dispose(); - } else { - if (obj.geometry) { - obj.geometry.dispose(); - } - if (obj.material) { - if (Array.isArray(obj.material)) { - for (const material of obj.material) { - material.dispose(); - } - } else { - obj.material.dispose(); - } - } - } -} - -function traverse(obj, callback) { - for (const child of obj.children) { - traverse(child, callback); - } - callback(obj); -} +import ObjectRemovalHelper from 'Process/ObjectRemovalHelper'; /** * Fires when the opacity of the layer has changed. @@ -174,23 +149,23 @@ class GeometryLayer extends Layer { } /** - * All layer's meshs are removed from scene and disposed from video device. + * All layer's 3D objects are removed from the scene and disposed from the video device. + * @param {boolean} [clearCache=false] Whether to clear the layer cache or not */ - delete() { + delete(clearCache) { + if (clearCache) { + this.cache.clear(); + } + // if Layer is attached if (this.parent) { - traverse(this.parent.object3d, (obj) => { - if (obj.layer && obj.layer.id == this.id) { - obj.parent.remove(obj); - disposeMesh(obj); - } - }); + ObjectRemovalHelper.removeChildrenAndCleanupRecursively(this, this.parent.object3d); } if (this.object3d.parent) { this.object3d.parent.remove(this.object3d); } - this.object3d.traverse(disposeMesh); + ObjectRemovalHelper.removeChildrenAndCleanupRecursively(this, this.object3d); } /** diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js index 90c2aba640..af7301262b 100644 --- a/src/Layer/LabelLayer.js +++ b/src/Layer/LabelLayer.js @@ -290,7 +290,14 @@ class LabelLayer extends Layer { } } - delete() { + /** + * All layer's objects and domElements are removed. + * @param {boolean} [clearCache=false] Whether to clear the layer cache or not + */ + delete(clearCache) { + if (clearCache) { + this.cache.clear(); + } this.domElement.parentElement.removeChild(this.domElement); this.parent.level0Nodes.forEach(obj => this.removeLabelsFromNodeRecursive(obj)); diff --git a/src/Layer/Layer.js b/src/Layer/Layer.js index a7ceb0e406..e6eb9fec54 100644 --- a/src/Layer/Layer.js +++ b/src/Layer/Layer.js @@ -236,9 +236,10 @@ class Layer extends THREE.EventDispatcher { /** * Remove and dispose all objects from layer. + * @param {boolean} [clearCache=false] Whether to clear the layer cache or not */ // eslint-disable-next-line - delete() { + delete(clearCache) { console.warn('Function delete doesn\'t exist for this layer'); } } diff --git a/src/Layer/OrientedImageLayer.js b/src/Layer/OrientedImageLayer.js index bdfe371bd2..af7f26701b 100644 --- a/src/Layer/OrientedImageLayer.js +++ b/src/Layer/OrientedImageLayer.js @@ -108,10 +108,14 @@ class OrientedImageLayer extends GeometryLayer { } super(id, new THREE.Group(), config); - this.background = config.background || createBackground(config.backgroundDistance); this.isOrientedImageLayer = true; + this.background = config.background || createBackground(config.backgroundDistance); + if (this.background) { + // Add layer id to easily identify the objects later on (e.g. to delete the geometries when deleting the layer) + this.background.layer = this.background.layer ?? {}; + this.background.layer.id = this.background.layer.id ?? id; this.object3d.add(this.background); } @@ -205,9 +209,16 @@ class OrientedImageLayer extends GeometryLayer { * Delete background, but doesn't delete OrientedImageLayer.material. For the moment, this material visibility is set to false. * You need to replace OrientedImageLayer.material applied on each object, if you want to continue displaying them. * This issue (see #1018 {@link https://github.com/iTowns/itowns/issues/1018}) will be fixed when OrientedImageLayer will be a ColorLayer. - */ - delete() { - super.delete(); + * @param {boolean} [clearCache=false] Whether to clear the layer cache or not + */ + delete(clearCache) { + if (this.background) { + // only delete geometries if it has some + super.delete(); + } + if (clearCache) { + this.cache.clear(); + } this.material.visible = false; console.warn('You need to replace OrientedImageLayer.material applied on each object. This issue will be fixed when OrientedImageLayer will be a ColorLayer. the material visibility is set to false. To follow issue see https://github.com/iTowns/itowns/issues/1018'); } diff --git a/src/Layer/RasterLayer.js b/src/Layer/RasterLayer.js index ba87ce7c45..5832413adf 100644 --- a/src/Layer/RasterLayer.js +++ b/src/Layer/RasterLayer.js @@ -15,8 +15,12 @@ class RasterLayer extends Layer { /** * All layer's textures are removed from scene and disposed from video device. + * @param {boolean} [clearCache=false] Whether to clear the layer cache or not */ - delete() { + delete(clearCache) { + if (clearCache) { + this.cache.clear(); + } for (const root of this.parent.level0Nodes) { root.traverse(removeLayeredMaterialNodeLayer(this.id)); } diff --git a/src/Process/ObjectRemovalHelper.js b/src/Process/ObjectRemovalHelper.js index 7cfd0bba2a..8c1bb7ddd6 100644 --- a/src/Process/ObjectRemovalHelper.js +++ b/src/Process/ObjectRemovalHelper.js @@ -1,3 +1,14 @@ +function disposeSingleMaterialAndTextures(material) { + material.dispose(); + // dispose textures + for (const key of Object.keys(material)) { + const val = material[key]; + if (val && val.isTexture) { + val.dispose(); + } + } +} + export default { /** * Cleanup obj to release three.js allocated resources @@ -6,7 +17,8 @@ export default { cleanup(obj) { obj.layer = null; - if (typeof obj.dispose === 'function') { + // THREE.Scene dispose function displays a deprecation message + if (!obj.isScene && typeof obj.dispose === 'function') { obj.dispose(); } else { if (obj.geometry) { @@ -20,10 +32,10 @@ export default { if (obj.material) { if (Array.isArray(obj.material)) { for (const material of obj.material) { - material.dispose(); + disposeSingleMaterialAndTextures(material); } } else { - obj.material.dispose(); + disposeSingleMaterialAndTextures(obj.material); } } obj.dispatchEvent({ type: 'dispose' }); @@ -44,7 +56,7 @@ export default { }, /** - * Remove obj's children belonging to a layer and cleanup objexts. + * Remove an obj and all its children belonging to a layer and only cleanup the obj (and not its children). * obj will be disposed but its children **won't**! * @param {Layer} layer The layer that objects must belong to. Other object are ignored * @param {Object3D} obj The Object3D we want to clean @@ -61,13 +73,15 @@ export default { }, /** - * Recursively remove obj's children belonging to a layer. + * Recursively remove an obj and all its children belonging to a layer. * All removed obj will have their geometry/material disposed. * @param {Layer} layer The layer that objects must belong to. Other object are ignored * @param {Object3D} obj The Object3D we want to clean * @return {Array} an array of removed Object3D from obj (not including the recursive removals) */ removeChildrenAndCleanupRecursively(layer, obj) { + // Objects are filtered by id because the obj hierarchy may also contain labels that have been added as childs + // of the objects which have their own removal logic let toRemove = obj.children.filter(c => (c.layer && c.layer.id) === layer.id); if (obj.link) { toRemove = toRemove.concat(obj.link);