From f7897e1d67955cbfac8d3c199c83e441f0271da5 Mon Sep 17 00:00:00 2001 From: Adrien Berthet Date: Wed, 27 Jun 2018 14:49:50 +0200 Subject: [PATCH 1/4] refactor: move geometric layers off views Move all contents of `createXXXLayer` in XXXView to XXXLayer in Prefab/XXX/. This declutters the XXXView files. This change applies for Globe, Planar and Panorama. In the same time, GeometryLayer has been extract from Layer and given some new methods which were repeated through each View. --- src/Core/Layer/GeometryLayer.js | 104 +++++++++++++++ src/Core/Layer/Layer.js | 48 +------ src/Core/MainLoop.js | 3 +- src/Core/Prefab/Globe/GlobeLayer.js | 63 +++++++++ src/Core/Prefab/GlobeView.js | 99 +------------- src/Core/Prefab/Panorama/PanoramaLayer.js | 118 ++++++++++++++++ src/Core/Prefab/PanoramaView.js | 155 +--------------------- src/Core/Prefab/Planar/PlanarLayer.js | 63 +++++++++ src/Core/Prefab/PlanarView.js | 99 +------------- src/Core/View.js | 3 +- src/Main.js | 3 +- 11 files changed, 368 insertions(+), 390 deletions(-) create mode 100644 src/Core/Layer/GeometryLayer.js create mode 100644 src/Core/Prefab/Globe/GlobeLayer.js create mode 100644 src/Core/Prefab/Panorama/PanoramaLayer.js create mode 100644 src/Core/Prefab/Planar/PlanarLayer.js diff --git a/src/Core/Layer/GeometryLayer.js b/src/Core/Layer/GeometryLayer.js new file mode 100644 index 0000000000..d065d0422c --- /dev/null +++ b/src/Core/Layer/GeometryLayer.js @@ -0,0 +1,104 @@ +import { EventDispatcher } from 'three'; +import Picking from '../Picking'; + +function GeometryLayer(id, object3d) { + if (!id) { + throw new Error('Missing id parameter (GeometryLayer must have a unique id defined)'); + } + if (!object3d || !object3d.isObject3D) { + throw new Error('Missing/Invalid object3d parameter (must be a three.js Object3D instance)'); + } + + this._attachedLayers = []; + this.type = 'geometry'; + this.protocol = 'tile'; + this.visible = true; + this.lighting = { + enable: false, + position: { x: -0.5, y: 0.0, z: 1.0 }, + }; + + if (object3d && object3d.type === 'Group' && object3d.name === '') { + object3d.name = id; + } + + Object.defineProperty(this, 'object3d', { + value: object3d, + writable: false, + }); + + Object.defineProperty(this, 'id', { + value: id, + writable: false, + }); + + // Setup default picking method + this.pickObjectsAt = (view, mouse, radius) => Picking.pickObjectsAt(view, mouse, radius, this.object3d); + + this.postUpdate = () => {}; +} + +GeometryLayer.prototype = Object.create(EventDispatcher.prototype); +GeometryLayer.prototype.constructor = GeometryLayer; + +GeometryLayer.prototype.attach = function attach(layer) { + if (!layer.update) { + throw new Error(`Missing 'update' function -> can't attach layer ${layer.id}`); + } + this._attachedLayers.push(layer); +}; + +GeometryLayer.prototype.detach = function detach(layer) { + const count = this._attachedLayers.length; + this._attachedLayers = this._attachedLayers.filter(attached => attached.id != layer.id); + return this._attachedLayers.length < count; +}; + +GeometryLayer.prototype.onTileCreated = function onTileCreated(layer, parent, node) { + node.material.setLightingOn(layer.lighting.enable); + node.material.uniforms.lightPosition.value = layer.lighting.position; + + if (layer.noTextureColor) { + node.material.uniforms.noTextureColor.value.copy(layer.noTextureColor); + } + + if (__DEBUG__) { + node.material.uniforms.showOutline = { value: layer.showOutline || false }; + node.material.wireframe = layer.wireframe || false; + } +}; + +GeometryLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { + let commonAncestor; + for (const source of changeSources.values()) { + if (source.isCamera) { + // if the change is caused by a camera move, no need to bother + // to find common ancestor: we need to update the whole tree: + // some invisible tiles may now be visible + return layer.level0Nodes; + } + if (source.layer === layer) { + if (!commonAncestor) { + commonAncestor = source; + } else { + commonAncestor = source.findCommonAncestor(commonAncestor); + if (!commonAncestor) { + return layer.level0Nodes; + } + } + if (commonAncestor.material == null) { + commonAncestor = undefined; + } + } + } + if (commonAncestor) { + if (__DEBUG__) { + layer._latestUpdateStartingLevel = commonAncestor.level; + } + return [commonAncestor]; + } else { + return layer.level0Nodes; + } +}; + +export default GeometryLayer; diff --git a/src/Core/Layer/Layer.js b/src/Core/Layer/Layer.js index f6ae6a233a..8518110152 100644 --- a/src/Core/Layer/Layer.js +++ b/src/Core/Layer/Layer.js @@ -1,5 +1,4 @@ import { EventDispatcher } from 'three'; -import Picking from '../Picking'; /** * Fires when layer sequence change (meaning when the order of the layer changes in the view) @@ -54,51 +53,6 @@ export const defineLayerProperty = function defineLayerProperty(layer, propertyN } }; -function GeometryLayer(id, object3d) { - if (!id) { - throw new Error('Missing id parameter (GeometryLayer must have a unique id defined)'); - } - if (!object3d || !object3d.isObject3D) { - throw new Error('Missing/Invalid object3d parameter (must be a three.js Object3D instance)'); - } - this._attachedLayers = []; - - if (object3d && object3d.type === 'Group' && object3d.name === '') { - object3d.name = id; - } - - Object.defineProperty(this, 'object3d', { - value: object3d, - writable: false, - }); - - Object.defineProperty(this, 'id', { - value: id, - writable: false, - }); - - // Setup default picking method - this.pickObjectsAt = (view, mouse, radius) => Picking.pickObjectsAt(view, mouse, radius, this.object3d); - - this.postUpdate = () => {}; -} - -GeometryLayer.prototype = Object.create(EventDispatcher.prototype); -GeometryLayer.prototype.constructor = GeometryLayer; - -GeometryLayer.prototype.attach = function attach(layer) { - if (!layer.update) { - throw new Error(`Missing 'update' function -> can't attach layer ${layer.id}`); - } - this._attachedLayers.push(layer); -}; - -GeometryLayer.prototype.detach = function detach(layer) { - const count = this._attachedLayers.length; - this._attachedLayers = this._attachedLayers.filter(attached => attached.id != layer.id); - return this._attachedLayers.length < count; -}; - /** * Don't use directly constructor to instance a new Layer * use addLayer in {@link View} @@ -179,4 +133,4 @@ const ImageryLayers = { }, }; -export { GeometryLayer, Layer, ImageryLayers }; +export { Layer, ImageryLayers }; diff --git a/src/Core/MainLoop.js b/src/Core/MainLoop.js index bd7a1f7a3a..2cdab4a383 100644 --- a/src/Core/MainLoop.js +++ b/src/Core/MainLoop.js @@ -1,5 +1,6 @@ import { EventDispatcher } from 'three'; -import { GeometryLayer, Layer } from './Layer/Layer'; +import { Layer } from './Layer/Layer'; +import GeometryLayer from './Layer/GeometryLayer'; import Cache from '../Core/Scheduler/Cache'; export const RENDERING_PAUSED = 0; diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js new file mode 100644 index 0000000000..a42decd3e6 --- /dev/null +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -0,0 +1,63 @@ +import * as THREE from 'three'; + +import GeometryLayer from '../../Layer/GeometryLayer'; + +import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; +import { globeCulling, preGlobeUpdate, globeSubdivisionControl, globeSchemeTileWMTS, globeSchemeTile1 } from '../../../Process/GlobeTileProcessing'; +import BuilderEllipsoidTile from './BuilderEllipsoidTile'; +import SubdivisionControl from '../../../Process/SubdivisionControl'; +import Picking from '../../Picking'; + +/** + * A geometry layer to be used only with a {@link GlobeView}. + * + * @constructor + * + * @param {string} id + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} [options.maxSubdivisionLevel=18] + * @param {number} [options.sseSubdivisionThreshold=1] + * @param {number} [options.maxDeltaElevationLevel=4] + */ +function GlobeLayer(id, options) { + GeometryLayer.call(this, id, options.object3d || new THREE.Group()); + + // Configure tiles + this.schemeTile = globeSchemeTileWMTS(globeSchemeTile1); + this.extent = this.schemeTile[0].clone(); + for (let i = 1; i < this.schemeTile.length; i++) { + this.extent.union(this.schemeTile[i]); + } + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return globeSubdivisionControl(2, + options.maxSubdivisionLevel || 18, + options.sseSubdivisionThreshold || 1.0, + options.maxDeltaElevationLevel || 4)(context, layer, node); + } + return false; + } + + this.update = processTiledGeometryNode(globeCulling(2), subdivision); + this.builder = new BuilderEllipsoidTile(); + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius = 5) => Picking.pickTilesAt(_view, mouse, radius, this); +} + +GlobeLayer.prototype = Object.create(GeometryLayer.prototype); +GlobeLayer.prototype.constructor = GlobeLayer; + +GlobeLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { + SubdivisionControl.preUpdate(context, layer); + + if (__DEBUG__) { + layer._latestUpdateStartingLevel = 0; + } + + preGlobeUpdate(context, layer); + + return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); +}; + +export default GlobeLayer; diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index 27aab463c8..f5961e1359 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -6,17 +6,11 @@ import { COLOR_LAYERS_ORDER_CHANGED } from '../../Renderer/ColorLayersOrdering'; import RendererConstant from '../../Renderer/RendererConstant'; import GlobeControls from '../../Renderer/ThreeExtended/GlobeControls'; -import { GeometryLayer } from '../Layer/Layer'; - +import GlobeLayer from './Globe/GlobeLayer'; import Atmosphere from './Globe/Atmosphere'; import CoordStars from '../Geographic/CoordStars'; import Coordinates, { C, ellipsoidSizes } from '../Geographic/Coordinates'; -import { processTiledGeometryNode } from '../../Process/TiledNodeProcessing'; -import { globeCulling, preGlobeUpdate, globeSubdivisionControl, globeSchemeTileWMTS, globeSchemeTile1 } from '../../Process/GlobeTileProcessing'; -import BuilderEllipsoidTile from './Globe/BuilderEllipsoidTile'; -import SubdivisionControl from '../../Process/SubdivisionControl'; -import Picking from '../Picking'; /** * Fires when the view is completely loaded. Controls and view's functions can be called then. @@ -71,92 +65,9 @@ export const GLOBE_VIEW_EVENTS = { COLOR_LAYERS_ORDER_CHANGED, }; -export function createGlobeLayer(id, options) { - // Configure tiles - const nodeInitFn = function nodeInitFn(layer, parent, node) { - node.material.setLightingOn(layer.lighting.enable); - node.material.uniforms.lightPosition.value = layer.lighting.position; - if (layer.noTextureColor) { - node.material.uniforms.noTextureColor.value.copy(layer.noTextureColor); - } - - if (__DEBUG__) { - node.material.uniforms.showOutline = { value: layer.showOutline || false }; - node.material.wireframe = layer.wireframe || false; - } - }; - - const wgs84TileLayer = new GeometryLayer(id, options.object3d || new THREE.Group()); - wgs84TileLayer.schemeTile = globeSchemeTileWMTS(globeSchemeTile1); - wgs84TileLayer.extent = wgs84TileLayer.schemeTile[0].clone(); - for (let i = 1; i < wgs84TileLayer.schemeTile.length; i++) { - wgs84TileLayer.extent.union(wgs84TileLayer.schemeTile[i]); - } - wgs84TileLayer.preUpdate = (context, layer, changeSources) => { - SubdivisionControl.preUpdate(context, layer); - - if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; - } - - preGlobeUpdate(context, layer); - - let commonAncestor; - for (const source of changeSources.values()) { - if (source.isCamera) { - // if the change is caused by a camera move, no need to bother - // to find common ancestor: we need to update the whole tree: - // some invisible tiles may now be visible - return layer.level0Nodes; - } - if (source.layer === layer) { - if (!commonAncestor) { - commonAncestor = source; - } else { - commonAncestor = source.findCommonAncestor(commonAncestor); - if (!commonAncestor) { - return layer.level0Nodes; - } - } - if (commonAncestor.material == null) { - commonAncestor = undefined; - } - } - } - if (commonAncestor) { - if (__DEBUG__) { - layer._latestUpdateStartingLevel = commonAncestor.level; - } - return [commonAncestor]; - } else { - return layer.level0Nodes; - } - }; - - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return globeSubdivisionControl(2, - options.maxSubdivisionLevel || 18, - options.sseSubdivisionThreshold || 1.0, - options.maxDeltaElevationLevel || 4)(context, layer, node); - } - return false; - } - - wgs84TileLayer.update = processTiledGeometryNode(globeCulling(2), subdivision); - wgs84TileLayer.builder = new BuilderEllipsoidTile(); - wgs84TileLayer.onTileCreated = nodeInitFn; - wgs84TileLayer.type = 'geometry'; - wgs84TileLayer.protocol = 'tile'; - wgs84TileLayer.visible = true; - wgs84TileLayer.lighting = { - enable: false, - position: { x: -0.5, y: 0.0, z: 1.0 }, - }; - // provide custom pick function - wgs84TileLayer.pickObjectsAt = (_view, mouse, radius = 5) => Picking.pickTilesAt(_view, mouse, radius, wgs84TileLayer); - - return wgs84TileLayer; +export function createGlobeLayer(id, extent, options) { + console.warn('createGlobeLayer is deprecated, use the GlobeLayer class instead.'); + return new GlobeLayer(id, extent, options); } /** @@ -190,7 +101,7 @@ function GlobeView(viewerDiv, coordCarto, options = {}) { this.camera.camera3D.updateProjectionMatrix(); this.camera.camera3D.updateMatrixWorld(true); - const wgs84TileLayer = createGlobeLayer('globe', options); + const wgs84TileLayer = new GlobeLayer('globe', options); const sun = new THREE.DirectionalLight(); sun.position.set(-0.5, 0, 1); diff --git a/src/Core/Prefab/Panorama/PanoramaLayer.js b/src/Core/Prefab/Panorama/PanoramaLayer.js new file mode 100644 index 0000000000..e132c1d897 --- /dev/null +++ b/src/Core/Prefab/Panorama/PanoramaLayer.js @@ -0,0 +1,118 @@ +import * as THREE from 'three'; + +import GeometryLayer from '../../Layer/GeometryLayer'; +import Extent from '../../Geographic/Extent'; +import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; +import { panoramaCulling, panoramaSubdivisionControl } from '../../../Process/PanoramaTileProcessing'; +import PanoramaTileBuilder from './PanoramaTileBuilder'; +import SubdivisionControl from '../../../Process/SubdivisionControl'; +import ProjectionType from './Constants'; +import Picking from '../../Picking'; + +/** + * A geometry layer to be used only with a {@link PanoramaView}. + * + * @constructor + * + * @param {string} id + * @param {Coordinates} coordinates + * @param {string} type + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} options.ratio=1 + * @param {number} [options.maxSubdivisionLevel=10] + */ +function PanoramaLayer(id, coordinates, type, options) { + GeometryLayer.call(this, id, options.object3d || new THREE.Group()); + + coordinates.xyz(this.object3d.position); + this.object3d.quaternion.setFromUnitVectors( + new THREE.Vector3(0, 0, 1), coordinates.geodesicNormal); + this.object3d.updateMatrixWorld(true); + + // FIXME: add CRS = '0' support + this.extent = new Extent('EPSG:4326', { + west: -180, + east: 180, + north: 90, + south: -90, + }); + + if (type === ProjectionType.SPHERICAL) { + // equirectangular -> spherical geometry + this.schemeTile = [ + new Extent('EPSG:4326', { + west: -180, + east: 0, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 0, + east: 180, + north: 90, + south: -90, + })]; + } else if (type === ProjectionType.CYLINDRICAL) { + // cylindrical geometry + this.schemeTile = [ + new Extent('EPSG:4326', { + west: -180, + east: -90, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: -90, + east: 0, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 0, + east: 90, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 90, + east: 180, + north: 90, + south: -90, + })]; + } else { + throw new Error(`Unsupported panorama projection type ${type}. + Only ProjectionType.SPHERICAL and ProjectionType.CYLINDRICAL are supported`); + } + this.disableSkirt = true; + + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return panoramaSubdivisionControl( + options.maxSubdivisionLevel || 10, new THREE.Vector2(512, 256))(context, layer, node); + } + return false; + } + + this.update = processTiledGeometryNode(panoramaCulling, subdivision); + this.builder = new PanoramaTileBuilder(type, options.ratio); + this.segments = 8; + this.quality = 0.5; + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); +} + +PanoramaLayer.prototype = Object.create(GeometryLayer.prototype); +PanoramaLayer.prototype.constructor = PanoramaLayer; + +PanoramaLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { + SubdivisionControl.preUpdate(context, layer); + + if (__DEBUG__) { + layer._latestUpdateStartingLevel = 0; + } + + if (changeSources.has(undefined) || changeSources.size == 0) { + return layer.level0Nodes; + } + + return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); +}; + +export default PanoramaLayer; diff --git a/src/Core/Prefab/PanoramaView.js b/src/Core/Prefab/PanoramaView.js index 20b6ea7323..59286e1d9b 100644 --- a/src/Core/Prefab/PanoramaView.js +++ b/src/Core/Prefab/PanoramaView.js @@ -1,158 +1,11 @@ import * as THREE from 'three'; import View from '../View'; - -import { GeometryLayer } from '../Layer/Layer'; -import Extent from '../Geographic/Extent'; -import { processTiledGeometryNode } from '../../Process/TiledNodeProcessing'; -import { panoramaCulling, panoramaSubdivisionControl } from '../../Process/PanoramaTileProcessing'; -import PanoramaTileBuilder from './Panorama/PanoramaTileBuilder'; -import SubdivisionControl from '../../Process/SubdivisionControl'; -import ProjectionType from './Panorama/Constants'; -import Picking from '../Picking'; +import PanoramaLayer from './Panorama/PanoramaLayer'; export function createPanoramaLayer(id, coordinates, type, options = {}) { - const tileLayer = new GeometryLayer(id, options.object3d || new THREE.Group()); - - coordinates.xyz(tileLayer.object3d.position); - tileLayer.object3d.quaternion.setFromUnitVectors( - new THREE.Vector3(0, 0, 1), coordinates.geodesicNormal); - tileLayer.object3d.updateMatrixWorld(true); - - // FIXME: add CRS = '0' support - tileLayer.extent = new Extent('EPSG:4326', { - west: -180, - east: 180, - north: 90, - south: -90, - }); - - if (type === ProjectionType.SPHERICAL) { - // equirectangular -> spherical geometry - tileLayer.schemeTile = [ - new Extent('EPSG:4326', { - west: -180, - east: 0, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 0, - east: 180, - north: 90, - south: -90, - })]; - } else if (type === ProjectionType.CYLINDRICAL) { - // cylindrical geometry - tileLayer.schemeTile = [ - new Extent('EPSG:4326', { - west: -180, - east: -90, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: -90, - east: 0, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 0, - east: 90, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 90, - east: 180, - north: 90, - south: -90, - })]; - } else { - throw new Error(`Unsupported panorama projection type ${type}. - Only ProjectionType.SPHERICAL and ProjectionType.CYLINDRICAL are supported`); - } - tileLayer.disableSkirt = true; - - // Configure tiles - const nodeInitFn = function nodeInitFn(layer, parent, node) { - if (layer.noTextureColor) { - node.material.uniforms.noTextureColor.value.copy(layer.noTextureColor); - } - node.material.depthWrite = false; - - if (__DEBUG__) { - node.material.uniforms.showOutline = { value: layer.showOutline || false }; - node.material.wireframe = layer.wireframe || false; - } - }; - - tileLayer.preUpdate = (context, layer, changeSources) => { - SubdivisionControl.preUpdate(context, layer); - - if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; - } - - if (changeSources.has(undefined) || changeSources.size == 0) { - return layer.level0Nodes; - } - - let commonAncestor; - for (const source of changeSources.values()) { - if (source.isCamera) { - // if the change is caused by a camera move, no need to bother - // to find common ancestor: we need to update the whole tree: - // some invisible tiles may now be visible - return layer.level0Nodes; - } - if (source.layer === layer.id) { - if (!commonAncestor) { - commonAncestor = source; - } else { - commonAncestor = source.findCommonAncestor(commonAncestor); - if (!commonAncestor) { - return layer.level0Nodes; - } - } - if (commonAncestor.material == null) { - commonAncestor = undefined; - } - } - } - if (commonAncestor) { - if (__DEBUG__) { - layer._latestUpdateStartingLevel = commonAncestor.level; - } - return [commonAncestor]; - } else { - return layer.level0Nodes; - } - }; - - - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return panoramaSubdivisionControl( - options.maxSubdivisionLevel || 10, new THREE.Vector2(512, 256))(context, layer, node); - } - return false; - } - - tileLayer.update = processTiledGeometryNode(panoramaCulling, subdivision); - tileLayer.builder = new PanoramaTileBuilder(type, options.ratio); - tileLayer.onTileCreated = nodeInitFn; - tileLayer.type = 'geometry'; - tileLayer.protocol = 'tile'; - tileLayer.visible = true; - tileLayer.segments = 8; - tileLayer.quality = 0.5; - tileLayer.lighting = { - enable: false, - position: { x: -0.5, y: 0.0, z: 1.0 }, - }; - // provide custom pick function - tileLayer.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, tileLayer); - - - return tileLayer; + console.warn('createPanoramaLayer is deprecated, use the PanoramaLayer class instead.'); + return new PanoramaLayer(id, coordinates, type, options); } function PanoramaView(viewerDiv, coordinates, type, options = {}) { @@ -177,7 +30,7 @@ function PanoramaView(viewerDiv, coordinates, type, options = {}) { } camera.updateMatrixWorld(); - const tileLayer = createPanoramaLayer('panorama', coordinates, type, options); + const tileLayer = new PanoramaLayer('panorama', coordinates, type, options); View.prototype.addLayer.call(this, tileLayer); diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js new file mode 100644 index 0000000000..618a1ea6f9 --- /dev/null +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -0,0 +1,63 @@ +import * as THREE from 'three'; + +import GeometryLayer from '../../Layer/GeometryLayer'; + +import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; +import { planarCulling, planarSubdivisionControl, prePlanarUpdate } from '../../../Process/PlanarTileProcessing'; +import PlanarTileBuilder from './PlanarTileBuilder'; +import SubdivisionControl from '../../../Process/SubdivisionControl'; +import Picking from '../../Picking'; + +/** + * A geometry layer to be used only with a {@link PlanarView}. + * + * @constructor + * + * @param {string} id + * @param {Extent} extent - the extent to define the layer within + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} [options.maxSubdivisionLevel=5] + * @param {number} [options.maxDeltaElevationLevel=4] + */ +function PlanarLayer(id, extent, options) { + GeometryLayer.call(this, id, options.object3d || new THREE.Group()); + + this.extent = extent; + this.schemeTile = [extent]; + + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return planarSubdivisionControl( + options.maxSubdivisionLevel || 5, + options.maxDeltaElevationLevel || 4)(context, layer, node); + } + return false; + } + + this.update = processTiledGeometryNode(planarCulling, subdivision); + this.builder = new PlanarTileBuilder(); + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); +} + +PlanarLayer.prototype = Object.create(GeometryLayer.prototype); +PlanarLayer.prototype.constructor = PlanarLayer; + +PlanarLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { + SubdivisionControl.preUpdate(context, layer); + + prePlanarUpdate(context, layer); + + if (__DEBUG__) { + layer._latestUpdateStartingLevel = 0; + } + + if (changeSources.has(undefined) || changeSources.size == 0) { + return layer.level0Nodes; + } + + return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); +}; + +export default PlanarLayer; diff --git a/src/Core/Prefab/PlanarView.js b/src/Core/Prefab/PlanarView.js index 2b76f1446e..b153eba13d 100644 --- a/src/Core/Prefab/PlanarView.js +++ b/src/Core/Prefab/PlanarView.js @@ -4,102 +4,11 @@ import View from '../View'; import { RENDERING_PAUSED, MAIN_LOOP_EVENTS } from '../MainLoop'; import RendererConstant from '../../Renderer/RendererConstant'; -import { GeometryLayer } from '../Layer/Layer'; - -import { processTiledGeometryNode } from '../../Process/TiledNodeProcessing'; -import { planarCulling, planarSubdivisionControl, prePlanarUpdate } from '../../Process/PlanarTileProcessing'; -import PlanarTileBuilder from './Planar/PlanarTileBuilder'; -import SubdivisionControl from '../../Process/SubdivisionControl'; -import Picking from '../Picking'; +import PlanarLayer from './Planar/PlanarLayer'; export function createPlanarLayer(id, extent, options) { - const tileLayer = new GeometryLayer(id, options.object3d || new THREE.Group()); - tileLayer.extent = extent; - tileLayer.schemeTile = [extent]; - - // Configure tiles - const nodeInitFn = function nodeInitFn(layer, parent, node) { - node.material.setLightingOn(layer.lighting.enable); - node.material.uniforms.lightPosition.value = layer.lighting.position; - - if (layer.noTextureColor) { - node.material.uniforms.noTextureColor.value.copy(layer.noTextureColor); - } - - if (__DEBUG__) { - node.material.uniforms.showOutline = { value: layer.showOutline || false }; - node.material.wireframe = layer.wireframe || false; - } - }; - - tileLayer.preUpdate = (context, layer, changeSources) => { - SubdivisionControl.preUpdate(context, layer); - - prePlanarUpdate(context, layer); - - if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; - } - - if (changeSources.has(undefined) || changeSources.size == 0) { - return layer.level0Nodes; - } - - let commonAncestor; - for (const source of changeSources.values()) { - if (source.isCamera) { - // if the change is caused by a camera move, no need to bother - // to find common ancestor: we need to update the whole tree: - // some invisible tiles may now be visible - return layer.level0Nodes; - } - if (source.layer === layer) { - if (!commonAncestor) { - commonAncestor = source; - } else { - commonAncestor = source.findCommonAncestor(commonAncestor); - if (!commonAncestor) { - return layer.level0Nodes; - } - } - if (commonAncestor.material == null) { - commonAncestor = undefined; - } - } - } - if (commonAncestor) { - if (__DEBUG__) { - layer._latestUpdateStartingLevel = commonAncestor.level; - } - return [commonAncestor]; - } else { - return layer.level0Nodes; - } - }; - - - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return planarSubdivisionControl(options.maxSubdivisionLevel || 5, - options.maxDeltaElevationLevel || 4)(context, layer, node); - } - return false; - } - - tileLayer.update = processTiledGeometryNode(planarCulling, subdivision); - tileLayer.builder = new PlanarTileBuilder(); - tileLayer.onTileCreated = nodeInitFn; - tileLayer.type = 'geometry'; - tileLayer.protocol = 'tile'; - tileLayer.visible = true; - tileLayer.lighting = { - enable: false, - position: { x: -0.5, y: 0.0, z: 1.0 }, - }; - // provide custom pick function - tileLayer.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, tileLayer); - - return tileLayer; + console.warn('createPlanarLayer is deprecated, use the PlanarLayer class instead.'); + return new PlanarLayer(id, extent, options); } function PlanarView(viewerDiv, extent, options = {}) { @@ -122,7 +31,7 @@ function PlanarView(viewerDiv, extent, options = {}) { this.camera.camera3D.updateProjectionMatrix(); this.camera.camera3D.updateMatrixWorld(true); - const tileLayer = createPlanarLayer('planar', extent, options); + const tileLayer = new PlanarLayer('planar', extent, options); this.addLayer(tileLayer); diff --git a/src/Core/View.js b/src/Core/View.js index 6de58a5a3a..1543b27d84 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -4,7 +4,8 @@ import Camera from '../Renderer/Camera'; import MainLoop, { MAIN_LOOP_EVENTS, RENDERING_PAUSED } from './MainLoop'; import c3DEngine from '../Renderer/c3DEngine'; import { STRATEGY_MIN_NETWORK_TRAFFIC } from './Layer/LayerUpdateStrategy'; -import { GeometryLayer, Layer, defineLayerProperty } from './Layer/Layer'; +import { Layer, defineLayerProperty } from './Layer/Layer'; +import GeometryLayer from './Layer/GeometryLayer'; import Scheduler from './Scheduler/Scheduler'; import Picking from './Picking'; import { updateLayeredMaterialNodeImagery, updateLayeredMaterialNodeElevation } from '../Process/LayeredMaterialNodeProcessing'; diff --git a/src/Main.js b/src/Main.js index c95a00627e..4a6542cc54 100644 --- a/src/Main.js +++ b/src/Main.js @@ -1,6 +1,7 @@ export { default as Coordinates, UNIT } from './Core/Geographic/Coordinates'; export { default as Extent } from './Core/Geographic/Extent'; -export { GeometryLayer, ImageryLayers } from './Core/Layer/Layer'; +export { ImageryLayers } from './Core/Layer/Layer'; +export { default as GeometryLayer } from './Core/Layer/GeometryLayer'; export { STRATEGY_MIN_NETWORK_TRAFFIC, STRATEGY_GROUP, STRATEGY_PROGRESSIVE, STRATEGY_DICHOTOMY } from './Core/Layer/LayerUpdateStrategy'; export { default as GlobeView, GLOBE_VIEW_EVENTS, createGlobeLayer } from './Core/Prefab/GlobeView'; export { default as PlanarView, createPlanarLayer } from './Core/Prefab/PlanarView'; From 67ca2338ca0b941959aeb7c7a9bacc121b923744 Mon Sep 17 00:00:00 2001 From: Adrien Berthet Date: Thu, 28 Jun 2018 14:19:25 +0200 Subject: [PATCH 2/4] review --- src/Core/Layer/GeometryLayer.js | 26 +++++++++++------------ src/Core/MainLoop.js | 2 +- src/Core/Prefab/Globe/GlobeLayer.js | 10 ++++----- src/Core/Prefab/Panorama/PanoramaLayer.js | 10 ++++----- src/Core/Prefab/Planar/PlanarLayer.js | 12 +++++------ src/Process/3dTilesProcessing.js | 16 +++++++------- src/Process/PointCloudProcessing.js | 24 ++++++++++----------- src/Process/TiledNodeProcessing.js | 2 +- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/Core/Layer/GeometryLayer.js b/src/Core/Layer/GeometryLayer.js index d065d0422c..cff5edfa0b 100644 --- a/src/Core/Layer/GeometryLayer.js +++ b/src/Core/Layer/GeometryLayer.js @@ -54,36 +54,36 @@ GeometryLayer.prototype.detach = function detach(layer) { return this._attachedLayers.length < count; }; -GeometryLayer.prototype.onTileCreated = function onTileCreated(layer, parent, node) { - node.material.setLightingOn(layer.lighting.enable); - node.material.uniforms.lightPosition.value = layer.lighting.position; +GeometryLayer.prototype.onTileCreated = function onTileCreated(node) { + node.material.setLightingOn(this.lighting.enable); + node.material.uniforms.lightPosition.value = this.lighting.position; - if (layer.noTextureColor) { - node.material.uniforms.noTextureColor.value.copy(layer.noTextureColor); + if (this.noTextureColor) { + node.material.uniforms.noTextureColor.value.copy(this.noTextureColor); } if (__DEBUG__) { - node.material.uniforms.showOutline = { value: layer.showOutline || false }; - node.material.wireframe = layer.wireframe || false; + node.material.uniforms.showOutline = { value: this.showOutline || false }; + node.material.wireframe = this.wireframe || false; } }; -GeometryLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { +GeometryLayer.prototype.preUpdate = function preUpdate(context, changeSources) { let commonAncestor; for (const source of changeSources.values()) { if (source.isCamera) { // if the change is caused by a camera move, no need to bother // to find common ancestor: we need to update the whole tree: // some invisible tiles may now be visible - return layer.level0Nodes; + return this.level0Nodes; } - if (source.layer === layer) { + if (source.this === this) { if (!commonAncestor) { commonAncestor = source; } else { commonAncestor = source.findCommonAncestor(commonAncestor); if (!commonAncestor) { - return layer.level0Nodes; + return this.level0Nodes; } } if (commonAncestor.material == null) { @@ -93,11 +93,11 @@ GeometryLayer.prototype.preUpdate = function preUpdate(context, layer, changeSou } if (commonAncestor) { if (__DEBUG__) { - layer._latestUpdateStartingLevel = commonAncestor.level; + this._latestUpdateStartingLevel = commonAncestor.level; } return [commonAncestor]; } else { - return layer.level0Nodes; + return this.level0Nodes; } }; diff --git a/src/Core/MainLoop.js b/src/Core/MainLoop.js index 2cdab4a383..410c89fedc 100644 --- a/src/Core/MainLoop.js +++ b/src/Core/MainLoop.js @@ -117,7 +117,7 @@ MainLoop.prototype._update = function _update(view, updateSources, dt) { const srcs = filterChangeSources(updateSources, geometryLayer); if (srcs.size > 0) { // `preUpdate` returns an array of elements to update - const elementsToUpdate = geometryLayer.preUpdate(context, geometryLayer, srcs); + const elementsToUpdate = geometryLayer.preUpdate(context, srcs); // `update` is called in `updateElements`. updateElements(context, geometryLayer, elementsToUpdate); // `postUpdate` is called when this geom layer update process is finished diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index a42decd3e6..b0fedf6279 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -48,16 +48,16 @@ function GlobeLayer(id, options) { GlobeLayer.prototype = Object.create(GeometryLayer.prototype); GlobeLayer.prototype.constructor = GlobeLayer; -GlobeLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { - SubdivisionControl.preUpdate(context, layer); +GlobeLayer.prototype.preUpdate = function preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; + this._latestUpdateStartingLevel = 0; } - preGlobeUpdate(context, layer); + preGlobeUpdate(context, this); - return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); + return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); }; export default GlobeLayer; diff --git a/src/Core/Prefab/Panorama/PanoramaLayer.js b/src/Core/Prefab/Panorama/PanoramaLayer.js index e132c1d897..6d9898d734 100644 --- a/src/Core/Prefab/Panorama/PanoramaLayer.js +++ b/src/Core/Prefab/Panorama/PanoramaLayer.js @@ -101,18 +101,18 @@ function PanoramaLayer(id, coordinates, type, options) { PanoramaLayer.prototype = Object.create(GeometryLayer.prototype); PanoramaLayer.prototype.constructor = PanoramaLayer; -PanoramaLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { - SubdivisionControl.preUpdate(context, layer); +PanoramaLayer.prototype.preUpdate = function preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; + this._latestUpdateStartingLevel = 0; } if (changeSources.has(undefined) || changeSources.size == 0) { - return layer.level0Nodes; + return this.level0Nodes; } - return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); + return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); }; export default PanoramaLayer; diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index 618a1ea6f9..8555838abe 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -44,20 +44,20 @@ function PlanarLayer(id, extent, options) { PlanarLayer.prototype = Object.create(GeometryLayer.prototype); PlanarLayer.prototype.constructor = PlanarLayer; -PlanarLayer.prototype.preUpdate = function preUpdate(context, layer, changeSources) { - SubdivisionControl.preUpdate(context, layer); +PlanarLayer.prototype.preUpdate = function preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); - prePlanarUpdate(context, layer); + prePlanarUpdate(context, this); if (__DEBUG__) { - layer._latestUpdateStartingLevel = 0; + this._latestUpdateStartingLevel = 0; } if (changeSources.has(undefined) || changeSources.size == 0) { - return layer.level0Nodes; + return this.level0Nodes; } - return GeometryLayer.prototype.preUpdate.call(this, context, layer, changeSources); + return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); }; export default PlanarLayer; diff --git a/src/Process/3dTilesProcessing.js b/src/Process/3dTilesProcessing.js index 01231ea5cb..413075523a 100644 --- a/src/Process/3dTilesProcessing.js +++ b/src/Process/3dTilesProcessing.js @@ -187,8 +187,8 @@ function _cleanupObject3D(n) { n.remove(...n.children); } -export function pre3dTilesUpdate(context, layer) { - if (!layer.visible) { +export function pre3dTilesUpdate(context) { + if (!this.visible) { return []; } @@ -204,20 +204,20 @@ export function pre3dTilesUpdate(context, layer) { // once in a while, garbage collect if (Math.random() > 0.98) { // Make sure we don't clean root tile - layer.root.cleanableSince = undefined; + this.root.cleanableSince = undefined; // Browse const now = Date.now(); - for (const elt of layer._cleanableTiles) { - if ((now - elt.cleanableSince) > layer.cleanupDelay) { - cleanup3dTileset(layer, elt); + for (const elt of this._cleanableTiles) { + if ((now - elt.cleanableSince) > this.cleanupDelay) { + cleanup3dTileset(this, elt); } } - layer._cleanableTiles = layer._cleanableTiles.filter(n => (layer.tileIndex.index[n.tileId].loaded && n.cleanableSince)); + this._cleanableTiles = this._cleanableTiles.filter(n => (this.tileIndex.index[n.tileId].loaded && n.cleanableSince)); } - return [layer.root]; + return [this.root]; } const cameraLocalPosition = new THREE.Vector3(); diff --git a/src/Process/PointCloudProcessing.js b/src/Process/PointCloudProcessing.js index 92d7183873..77c4af6828 100644 --- a/src/Process/PointCloudProcessing.js +++ b/src/Process/PointCloudProcessing.js @@ -87,9 +87,9 @@ function markForDeletion(elt) { } export default { - preUpdate(context, layer, changeSources) { + preUpdate(context, changeSources) { // Bail-out if not ready - if (!layer.root) { + if (!this.root) { return []; } @@ -99,27 +99,27 @@ export default { context.camera.height / (2 * Math.tan(THREE.Math.degToRad(context.camera.camera3D.fov) * 0.5)); - if (layer.material) { - layer.material.visible = layer.visible; - layer.material.opacity = layer.opacity; - layer.material.transparent = layer.opacity < 1; - layer.material.size = layer.pointSize; + if (this.material) { + this.material.visible = this.visible; + this.material.opacity = this.opacity; + this.material.transparent = this.opacity < 1; + this.material.size = this.pointSize; } // lookup lowest common ancestor of changeSources let commonAncestorName; for (const source of changeSources.values()) { - if (source.isCamera || source == layer) { + if (source.isCamera || source == this) { // if the change is caused by a camera move, no need to bother // to find common ancestor: we need to update the whole tree: // some invisible tiles may now be visible - return [layer.root]; + return [this.root]; } if (source.obj === undefined) { continue; } // filter sources that belong to our layer - if (source.obj.isPoints && source.obj.layer == layer) { + if (source.obj.isPoints && source.obj.layer == this) { if (!commonAncestorName) { commonAncestorName = source.name; } else { @@ -137,11 +137,11 @@ export default { } } if (commonAncestorName) { - return [layer.root.findChildrenByName(commonAncestorName)]; + return [this.root.findChildrenByName(commonAncestorName)]; } // Start updating from hierarchy root - return [layer.root]; + return [this.root]; }, update(context, layer, elt) { diff --git a/src/Process/TiledNodeProcessing.js b/src/Process/TiledNodeProcessing.js index d3817bac7b..e921430c04 100644 --- a/src/Process/TiledNodeProcessing.js +++ b/src/Process/TiledNodeProcessing.js @@ -40,7 +40,7 @@ export function requestNewTile(view, scheduler, geometryLayer, extent, parent, l return scheduler.execute(command).then((node) => { node.add(node.OBB()); - geometryLayer.onTileCreated(geometryLayer, parent, node); + geometryLayer.onTileCreated(node); return node; }); } From 3893de06e1076290f2162eaeb9d927daa8c99abe Mon Sep 17 00:00:00 2001 From: Adrien Berthet Date: Fri, 29 Jun 2018 13:04:04 +0200 Subject: [PATCH 3/4] fix test --- test/pointcloudprocessing_unit_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pointcloudprocessing_unit_test.js b/test/pointcloudprocessing_unit_test.js index 537a7f32be..731ba1bac8 100644 --- a/test/pointcloudprocessing_unit_test.js +++ b/test/pointcloudprocessing_unit_test.js @@ -11,7 +11,7 @@ describe('preUpdate', function () { const sources = new Set(); assert.equal( layer.root, - PointCloudProcessing.preUpdate(context, layer, sources)[0]); + PointCloudProcessing.preUpdate.call(layer, context, sources)[0]); }); it('should return root if no common ancestors', () => { @@ -23,7 +23,7 @@ describe('preUpdate', function () { sources.add(elt2); assert.equal( layer.root, - PointCloudProcessing.preUpdate(context, layer, sources)[0]); + PointCloudProcessing.preUpdate.call(layer, context, sources)[0]); }); it('should return common ancestor', () => { From 0682f2ccad978d06a6567d4e1f5a2b0a72937587 Mon Sep 17 00:00:00 2001 From: Adrien Berthet Date: Thu, 5 Jul 2018 15:21:42 +0200 Subject: [PATCH 4/4] update and add color layer, elevation layer and tiledgeometrylayer --- src/Core/Layer/GeometryLayer.js | 104 ---------- src/Core/MainLoop.js | 4 +- src/Core/Prefab/Globe/GlobeLayer.js | 89 +++++---- src/Core/Prefab/Panorama/PanoramaLayer.js | 188 +++++++++---------- src/Core/Prefab/Planar/PlanarLayer.js | 87 +++++---- src/Core/View.js | 97 ++++++---- src/Layer/ColorLayer.js | 14 ++ src/Layer/ElevationLayer.js | 11 ++ src/Layer/GeometryLayer.js | 42 +++++ src/{Core => }/Layer/Layer.js | 122 ++++++------ src/{Core => }/Layer/LayerUpdateState.js | 0 src/{Core => }/Layer/LayerUpdateStrategy.js | 2 +- src/Layer/TiledGeometryLayer.js | 63 +++++++ src/Main.js | 6 +- src/Process/FeatureProcessing.js | 2 +- src/Process/LayeredMaterialNodeProcessing.js | 6 +- src/Provider/PointCloudProvider.js | 1 - src/Renderer/ColorLayersOrdering.js | 2 +- 18 files changed, 450 insertions(+), 390 deletions(-) delete mode 100644 src/Core/Layer/GeometryLayer.js create mode 100644 src/Layer/ColorLayer.js create mode 100644 src/Layer/ElevationLayer.js create mode 100644 src/Layer/GeometryLayer.js rename src/{Core => }/Layer/Layer.js (55%) rename src/{Core => }/Layer/LayerUpdateState.js (100%) rename src/{Core => }/Layer/LayerUpdateStrategy.js (97%) create mode 100644 src/Layer/TiledGeometryLayer.js diff --git a/src/Core/Layer/GeometryLayer.js b/src/Core/Layer/GeometryLayer.js deleted file mode 100644 index cff5edfa0b..0000000000 --- a/src/Core/Layer/GeometryLayer.js +++ /dev/null @@ -1,104 +0,0 @@ -import { EventDispatcher } from 'three'; -import Picking from '../Picking'; - -function GeometryLayer(id, object3d) { - if (!id) { - throw new Error('Missing id parameter (GeometryLayer must have a unique id defined)'); - } - if (!object3d || !object3d.isObject3D) { - throw new Error('Missing/Invalid object3d parameter (must be a three.js Object3D instance)'); - } - - this._attachedLayers = []; - this.type = 'geometry'; - this.protocol = 'tile'; - this.visible = true; - this.lighting = { - enable: false, - position: { x: -0.5, y: 0.0, z: 1.0 }, - }; - - if (object3d && object3d.type === 'Group' && object3d.name === '') { - object3d.name = id; - } - - Object.defineProperty(this, 'object3d', { - value: object3d, - writable: false, - }); - - Object.defineProperty(this, 'id', { - value: id, - writable: false, - }); - - // Setup default picking method - this.pickObjectsAt = (view, mouse, radius) => Picking.pickObjectsAt(view, mouse, radius, this.object3d); - - this.postUpdate = () => {}; -} - -GeometryLayer.prototype = Object.create(EventDispatcher.prototype); -GeometryLayer.prototype.constructor = GeometryLayer; - -GeometryLayer.prototype.attach = function attach(layer) { - if (!layer.update) { - throw new Error(`Missing 'update' function -> can't attach layer ${layer.id}`); - } - this._attachedLayers.push(layer); -}; - -GeometryLayer.prototype.detach = function detach(layer) { - const count = this._attachedLayers.length; - this._attachedLayers = this._attachedLayers.filter(attached => attached.id != layer.id); - return this._attachedLayers.length < count; -}; - -GeometryLayer.prototype.onTileCreated = function onTileCreated(node) { - node.material.setLightingOn(this.lighting.enable); - node.material.uniforms.lightPosition.value = this.lighting.position; - - if (this.noTextureColor) { - node.material.uniforms.noTextureColor.value.copy(this.noTextureColor); - } - - if (__DEBUG__) { - node.material.uniforms.showOutline = { value: this.showOutline || false }; - node.material.wireframe = this.wireframe || false; - } -}; - -GeometryLayer.prototype.preUpdate = function preUpdate(context, changeSources) { - let commonAncestor; - for (const source of changeSources.values()) { - if (source.isCamera) { - // if the change is caused by a camera move, no need to bother - // to find common ancestor: we need to update the whole tree: - // some invisible tiles may now be visible - return this.level0Nodes; - } - if (source.this === this) { - if (!commonAncestor) { - commonAncestor = source; - } else { - commonAncestor = source.findCommonAncestor(commonAncestor); - if (!commonAncestor) { - return this.level0Nodes; - } - } - if (commonAncestor.material == null) { - commonAncestor = undefined; - } - } - } - if (commonAncestor) { - if (__DEBUG__) { - this._latestUpdateStartingLevel = commonAncestor.level; - } - return [commonAncestor]; - } else { - return this.level0Nodes; - } -}; - -export default GeometryLayer; diff --git a/src/Core/MainLoop.js b/src/Core/MainLoop.js index 410c89fedc..b722bed346 100644 --- a/src/Core/MainLoop.js +++ b/src/Core/MainLoop.js @@ -1,6 +1,6 @@ import { EventDispatcher } from 'three'; -import { Layer } from './Layer/Layer'; -import GeometryLayer from './Layer/GeometryLayer'; +import Layer from '../Layer/Layer'; +import GeometryLayer from '../Layer/GeometryLayer'; import Cache from '../Core/Scheduler/Cache'; export const RENDERING_PAUSED = 0; diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index b0fedf6279..900e8b8454 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import GeometryLayer from '../../Layer/GeometryLayer'; +import TiledGeometryLayer from '../../../Layer/TiledGeometryLayer'; import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; import { globeCulling, preGlobeUpdate, globeSubdivisionControl, globeSchemeTileWMTS, globeSchemeTile1 } from '../../../Process/GlobeTileProcessing'; @@ -8,56 +8,55 @@ import BuilderEllipsoidTile from './BuilderEllipsoidTile'; import SubdivisionControl from '../../../Process/SubdivisionControl'; import Picking from '../../Picking'; -/** - * A geometry layer to be used only with a {@link GlobeView}. - * - * @constructor - * - * @param {string} id - * @param {Object} options - * @param {THREE.Object3D} options.object3d - * @param {number} [options.maxSubdivisionLevel=18] - * @param {number} [options.sseSubdivisionThreshold=1] - * @param {number} [options.maxDeltaElevationLevel=4] - */ -function GlobeLayer(id, options) { - GeometryLayer.call(this, id, options.object3d || new THREE.Group()); - - // Configure tiles - this.schemeTile = globeSchemeTileWMTS(globeSchemeTile1); - this.extent = this.schemeTile[0].clone(); - for (let i = 1; i < this.schemeTile.length; i++) { - this.extent.union(this.schemeTile[i]); - } - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return globeSubdivisionControl(2, - options.maxSubdivisionLevel || 18, - options.sseSubdivisionThreshold || 1.0, - options.maxDeltaElevationLevel || 4)(context, layer, node); +class GlobeLayer extends TiledGeometryLayer { + /** + * A geometry layer to be used only with a {@link GlobeView}. + * + * @constructor + * + * @param {string} id + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} [options.maxSubdivisionLevel=18] + * @param {number} [options.sseSubdivisionThreshold=1] + * @param {number} [options.maxDeltaElevationLevel=4] + */ + constructor(id, options) { + super(id, options.object3d || new THREE.Group()); + + // Configure tiles + this.schemeTile = globeSchemeTileWMTS(globeSchemeTile1); + this.extent = this.schemeTile[0].clone(); + for (let i = 1; i < this.schemeTile.length; i++) { + this.extent.union(this.schemeTile[i]); + } + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return globeSubdivisionControl(2, + options.maxSubdivisionLevel || 18, + options.sseSubdivisionThreshold || 1.0, + options.maxDeltaElevationLevel || 4)(context, layer, node); + } + return false; } - return false; + + this.update = processTiledGeometryNode(globeCulling(2), subdivision); + this.builder = new BuilderEllipsoidTile(); + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius = 5) => Picking.pickTilesAt(_view, mouse, radius, this); } - this.update = processTiledGeometryNode(globeCulling(2), subdivision); - this.builder = new BuilderEllipsoidTile(); - // provide custom pick function - this.pickObjectsAt = (_view, mouse, radius = 5) => Picking.pickTilesAt(_view, mouse, radius, this); -} + preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); -GlobeLayer.prototype = Object.create(GeometryLayer.prototype); -GlobeLayer.prototype.constructor = GlobeLayer; + if (__DEBUG__) { + this._latestUpdateStartingLevel = 0; + } -GlobeLayer.prototype.preUpdate = function preUpdate(context, changeSources) { - SubdivisionControl.preUpdate(context, this); + preGlobeUpdate(context, this); - if (__DEBUG__) { - this._latestUpdateStartingLevel = 0; + return super.preUpdate(context, changeSources); } - - preGlobeUpdate(context, this); - - return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); -}; +} export default GlobeLayer; diff --git a/src/Core/Prefab/Panorama/PanoramaLayer.js b/src/Core/Prefab/Panorama/PanoramaLayer.js index 6d9898d734..01e9e17da0 100644 --- a/src/Core/Prefab/Panorama/PanoramaLayer.js +++ b/src/Core/Prefab/Panorama/PanoramaLayer.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import GeometryLayer from '../../Layer/GeometryLayer'; +import TiledGeometryLayer from '../../../Layer/TiledGeometryLayer'; import Extent from '../../Geographic/Extent'; import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; import { panoramaCulling, panoramaSubdivisionControl } from '../../../Process/PanoramaTileProcessing'; @@ -9,110 +9,110 @@ import SubdivisionControl from '../../../Process/SubdivisionControl'; import ProjectionType from './Constants'; import Picking from '../../Picking'; -/** - * A geometry layer to be used only with a {@link PanoramaView}. - * - * @constructor - * - * @param {string} id - * @param {Coordinates} coordinates - * @param {string} type - * @param {Object} options - * @param {THREE.Object3D} options.object3d - * @param {number} options.ratio=1 - * @param {number} [options.maxSubdivisionLevel=10] - */ -function PanoramaLayer(id, coordinates, type, options) { - GeometryLayer.call(this, id, options.object3d || new THREE.Group()); +class PanoramaLayer extends TiledGeometryLayer { + /** + * A geometry layer to be used only with a {@link PanoramaView}. + * + * @constructor + * + * @param {string} id + * @param {Coordinates} coordinates + * @param {string} type + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} options.ratio=1 + * @param {number} [options.maxSubdivisionLevel=10] + */ + constructor(id, coordinates, type, options) { + super(id, options.object3d || new THREE.Group()); - coordinates.xyz(this.object3d.position); - this.object3d.quaternion.setFromUnitVectors( - new THREE.Vector3(0, 0, 1), coordinates.geodesicNormal); - this.object3d.updateMatrixWorld(true); + coordinates.xyz(this.object3d.position); + this.object3d.quaternion.setFromUnitVectors( + new THREE.Vector3(0, 0, 1), coordinates.geodesicNormal); + this.object3d.updateMatrixWorld(true); - // FIXME: add CRS = '0' support - this.extent = new Extent('EPSG:4326', { - west: -180, - east: 180, - north: 90, - south: -90, - }); + // FIXME: add CRS = '0' support + this.extent = new Extent('EPSG:4326', { + west: -180, + east: 180, + north: 90, + south: -90, + }); - if (type === ProjectionType.SPHERICAL) { - // equirectangular -> spherical geometry - this.schemeTile = [ - new Extent('EPSG:4326', { - west: -180, - east: 0, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 0, - east: 180, - north: 90, - south: -90, - })]; - } else if (type === ProjectionType.CYLINDRICAL) { - // cylindrical geometry - this.schemeTile = [ - new Extent('EPSG:4326', { - west: -180, - east: -90, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: -90, - east: 0, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 0, - east: 90, - north: 90, - south: -90, - }), new Extent('EPSG:4326', { - west: 90, - east: 180, - north: 90, - south: -90, - })]; - } else { - throw new Error(`Unsupported panorama projection type ${type}. - Only ProjectionType.SPHERICAL and ProjectionType.CYLINDRICAL are supported`); - } - this.disableSkirt = true; + if (type === ProjectionType.SPHERICAL) { + // equirectangular -> spherical geometry + this.schemeTile = [ + new Extent('EPSG:4326', { + west: -180, + east: 0, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 0, + east: 180, + north: 90, + south: -90, + })]; + } else if (type === ProjectionType.CYLINDRICAL) { + // cylindrical geometry + this.schemeTile = [ + new Extent('EPSG:4326', { + west: -180, + east: -90, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: -90, + east: 0, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 0, + east: 90, + north: 90, + south: -90, + }), new Extent('EPSG:4326', { + west: 90, + east: 180, + north: 90, + south: -90, + })]; + } else { + throw new Error(`Unsupported panorama projection type ${type}. + Only ProjectionType.SPHERICAL and ProjectionType.CYLINDRICAL are supported`); + } + this.disableSkirt = true; - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return panoramaSubdivisionControl( - options.maxSubdivisionLevel || 10, new THREE.Vector2(512, 256))(context, layer, node); + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return panoramaSubdivisionControl( + options.maxSubdivisionLevel || 10, new THREE.Vector2(512, 256))(context, layer, node); + } + return false; } - return false; + + this.update = processTiledGeometryNode(panoramaCulling, subdivision); + this.builder = new PanoramaTileBuilder(type, options.ratio); + this.segments = 8; + this.quality = 0.5; + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); } - this.update = processTiledGeometryNode(panoramaCulling, subdivision); - this.builder = new PanoramaTileBuilder(type, options.ratio); - this.segments = 8; - this.quality = 0.5; - // provide custom pick function - this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); -} -PanoramaLayer.prototype = Object.create(GeometryLayer.prototype); -PanoramaLayer.prototype.constructor = PanoramaLayer; + preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); -PanoramaLayer.prototype.preUpdate = function preUpdate(context, changeSources) { - SubdivisionControl.preUpdate(context, this); + if (__DEBUG__) { + this._latestUpdateStartingLevel = 0; + } - if (__DEBUG__) { - this._latestUpdateStartingLevel = 0; - } + if (changeSources.has(undefined) || changeSources.size == 0) { + return this.level0Nodes; + } - if (changeSources.has(undefined) || changeSources.size == 0) { - return this.level0Nodes; + return super.preUpdate(context, changeSources); } - - return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); -}; +} export default PanoramaLayer; diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index 8555838abe..7f412b4e79 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import GeometryLayer from '../../Layer/GeometryLayer'; +import TiledGeometryLayer from '../../../Layer/TiledGeometryLayer'; import { processTiledGeometryNode } from '../../../Process/TiledNodeProcessing'; import { planarCulling, planarSubdivisionControl, prePlanarUpdate } from '../../../Process/PlanarTileProcessing'; @@ -8,56 +8,55 @@ import PlanarTileBuilder from './PlanarTileBuilder'; import SubdivisionControl from '../../../Process/SubdivisionControl'; import Picking from '../../Picking'; -/** - * A geometry layer to be used only with a {@link PlanarView}. - * - * @constructor - * - * @param {string} id - * @param {Extent} extent - the extent to define the layer within - * @param {Object} options - * @param {THREE.Object3D} options.object3d - * @param {number} [options.maxSubdivisionLevel=5] - * @param {number} [options.maxDeltaElevationLevel=4] - */ -function PlanarLayer(id, extent, options) { - GeometryLayer.call(this, id, options.object3d || new THREE.Group()); - - this.extent = extent; - this.schemeTile = [extent]; - - function subdivision(context, layer, node) { - if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { - return planarSubdivisionControl( - options.maxSubdivisionLevel || 5, - options.maxDeltaElevationLevel || 4)(context, layer, node); +class PlanarLayer extends TiledGeometryLayer { + /** + * A geometry layer to be used only with a {@link PlanarView}. + * + * @constructor + * + * @param {string} id + * @param {Extent} extent - the extent to define the layer within + * @param {Object} options + * @param {THREE.Object3D} options.object3d + * @param {number} [options.maxSubdivisionLevel=5] + * @param {number} [options.maxDeltaElevationLevel=4] + */ + constructor(id, extent, options) { + super(id, options.object3d || new THREE.Group()); + + this.extent = extent; + this.schemeTile = [extent]; + + function subdivision(context, layer, node) { + if (SubdivisionControl.hasEnoughTexturesToSubdivide(context, layer, node)) { + return planarSubdivisionControl( + options.maxSubdivisionLevel || 5, + options.maxDeltaElevationLevel || 4)(context, layer, node); + } + return false; } - return false; - } - this.update = processTiledGeometryNode(planarCulling, subdivision); - this.builder = new PlanarTileBuilder(); - // provide custom pick function - this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); -} + this.update = processTiledGeometryNode(planarCulling, subdivision); + this.builder = new PlanarTileBuilder(); + // provide custom pick function + this.pickObjectsAt = (_view, mouse, radius) => Picking.pickTilesAt(_view, mouse, radius, this); + } -PlanarLayer.prototype = Object.create(GeometryLayer.prototype); -PlanarLayer.prototype.constructor = PlanarLayer; + preUpdate(context, changeSources) { + SubdivisionControl.preUpdate(context, this); -PlanarLayer.prototype.preUpdate = function preUpdate(context, changeSources) { - SubdivisionControl.preUpdate(context, this); + prePlanarUpdate(context, this); - prePlanarUpdate(context, this); + if (__DEBUG__) { + this._latestUpdateStartingLevel = 0; + } - if (__DEBUG__) { - this._latestUpdateStartingLevel = 0; - } + if (changeSources.has(undefined) || changeSources.size == 0) { + return this.level0Nodes; + } - if (changeSources.has(undefined) || changeSources.size == 0) { - return this.level0Nodes; + return super.preUpdate(context, changeSources); } - - return GeometryLayer.prototype.preUpdate.call(this, context, changeSources); -}; +} export default PlanarLayer; diff --git a/src/Core/View.js b/src/Core/View.js index 1543b27d84..2bf9c14af5 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -1,11 +1,15 @@ /* global window */ -import { Scene, EventDispatcher, Vector2, Object3D } from 'three'; +import * as THREE from 'three'; import Camera from '../Renderer/Camera'; import MainLoop, { MAIN_LOOP_EVENTS, RENDERING_PAUSED } from './MainLoop'; import c3DEngine from '../Renderer/c3DEngine'; -import { STRATEGY_MIN_NETWORK_TRAFFIC } from './Layer/LayerUpdateStrategy'; -import { Layer, defineLayerProperty } from './Layer/Layer'; -import GeometryLayer from './Layer/GeometryLayer'; + +import { STRATEGY_MIN_NETWORK_TRAFFIC } from '../Layer/LayerUpdateStrategy'; +import Layer from '../Layer/Layer'; +import ColorLayer from '../Layer/ColorLayer'; +import ElevationLayer from '../Layer/ElevationLayer'; +import GeometryLayer from '../Layer/GeometryLayer'; + import Scheduler from './Scheduler/Scheduler'; import Picking from './Picking'; import { updateLayeredMaterialNodeImagery, updateLayeredMaterialNodeElevation } from '../Process/LayeredMaterialNodeProcessing'; @@ -60,7 +64,7 @@ function View(crs, viewerDiv, options = {}) { this.mainLoop = options.mainLoop || new MainLoop(new Scheduler(), engine); - this.scene = options.scene3D || new Scene(); + this.scene = options.scene3D || new THREE.Scene(); if (!options.scene3D) { this.scene.autoUpdate = false; } @@ -77,7 +81,7 @@ function View(crs, viewerDiv, options = {}) { window.addEventListener('resize', () => { // If the user gave us a container (
) then itowns' size is // the container's size. Otherwise we use window' size. - const newSize = new Vector2(viewerDiv.clientWidth, viewerDiv.clientHeight); + const newSize = new THREE.Vector2(viewerDiv.clientWidth, viewerDiv.clientHeight); this.mainLoop.gfxEngine.onWindowResize(newSize.x, newSize.y); this.notifyChange(this.camera.camera3D); }, false); @@ -102,9 +106,43 @@ function View(crs, viewerDiv, options = {}) { }; } -View.prototype = Object.create(EventDispatcher.prototype); +View.prototype = Object.create(THREE.EventDispatcher.prototype); View.prototype.constructor = View; +function _createLayerFromConfig(config) { + let layer; + + switch (config.type) { + case 'color': + layer = new ColorLayer(config.id); + break; + case 'elevation': + layer = new ElevationLayer(config.id); + break; + case 'geometry': + layer = new GeometryLayer(config.id, new THREE.Group()); + break; + case 'debug': + layer = new Layer(config.id, 'debug'); + break; + default: + throw new Error(`Unknown layer type ${config.type}: please + specify a valid one`); + } + + // nlayer.id and type are read-only so delete them from layer before + // Object.assign + const tmp = config; + delete tmp.id; + delete tmp.type; + layer = Object.assign(layer, config); + // restore layer.id and type in user provider layer object + tmp.id = config.id; + tmp.type = config.type; + + return layer; +} + const _syncGeometryLayerVisibility = function _syncGeometryLayerVisibility(layer, view) { if (layer.object3d) { layer.object3d.visible = layer.visible; @@ -120,14 +158,8 @@ const _syncGeometryLayerVisibility = function _syncGeometryLayerVisibility(layer }; function _preprocessLayer(view, layer, provider, parentLayer) { - if (!(layer instanceof Layer) && !(layer instanceof GeometryLayer)) { - const nlayer = new Layer(layer.id); - // nlayer.id is read-only so delete it from layer before Object.assign - const tmp = layer; - delete tmp.id; - layer = Object.assign(nlayer, layer); - // restore layer.id in user provider layer object - tmp.id = layer.id; + if (!(layer instanceof Layer)) { + layer = _createLayerFromConfig(layer); } layer.options = layer.options || {}; @@ -176,17 +208,9 @@ function _preprocessLayer(view, layer, provider, parentLayer) { }); } - // probably not the best place to do this - if (layer.type == 'color') { - defineLayerProperty(layer, 'frozen', false); - defineLayerProperty(layer, 'visible', true); - defineLayerProperty(layer, 'opacity', 1.0); - defineLayerProperty(layer, 'sequence', 0); - } else if (layer.type == 'elevation') { - defineLayerProperty(layer, 'frozen', false); - } else if (layer.type == 'geometry' || layer.type == 'debug') { - defineLayerProperty(layer, 'visible', true, () => _syncGeometryLayerVisibility(layer, view)); - defineLayerProperty(layer, 'frozen', false); + if (layer.type == 'geometry' || layer.type == 'debug') { + layer.defineLayerProperty('visible', true, () => _syncGeometryLayerVisibility(layer, view)); + layer.defineLayerProperty('frozen', false); _syncGeometryLayerVisibility(layer, view); const changeOpacity = (o) => { @@ -202,7 +226,7 @@ function _preprocessLayer(view, layer, provider, parentLayer) { } } }; - defineLayerProperty(layer, 'opacity', 1.0, () => { + layer.defineLayerProperty('opacity', 1.0, () => { if (layer.object3d) { layer.object3d.traverse((o) => { if (o.layer !== layer) { @@ -217,6 +241,7 @@ function _preprocessLayer(view, layer, provider, parentLayer) { } }); } + return layer; } @@ -397,12 +422,12 @@ View.prototype.notifyChange = function notifyChange(changeSource = undefined, ne */ View.prototype.getLayers = function getLayers(filter) { const result = []; - for (const geometryLayer of this._layers) { - if (!filter || filter(geometryLayer)) { - result.push(geometryLayer); + for (const layer of this._layers) { + if (!filter || filter(layer)) { + result.push(layer); } - for (const attached of geometryLayer._attachedLayers) { - if (!filter || filter(attached, geometryLayer)) { + for (const attached of layer._attachedLayers) { + if (!filter || filter(attached, layer)) { result.push(attached); } } @@ -538,7 +563,7 @@ View.prototype.execFrameRequesters = function execFrameRequesters(when, dt, upda } }; -const _eventCoords = new Vector2(); +const _eventCoords = new THREE.Vector2(); /** * Extract view coordinates from a mouse-event / touch-event * @param {event} event - event can be a MouseEvent or a TouchEvent @@ -568,7 +593,7 @@ View.prototype.eventToNormalizedCoords = function eventToNormalizedCoords(event, /** * Convert view coordinates to normalized coordinates (NDC) - * @param {Vector2} viewCoords (in pixels, 0-0 = top-left of the View) + * @param {THREE.Vector2} viewCoords (in pixels, 0-0 = top-left of the View) * @return {THREE.Vector2} - NDC coordinates (x and y are [-1, 1]) */ View.prototype.viewToNormalizedCoords = function viewToNormalizedCoords(viewCoords) { @@ -579,7 +604,7 @@ View.prototype.viewToNormalizedCoords = function viewToNormalizedCoords(viewCoor /** * Convert NDC coordinates to view coordinates - * @param {Vector2} ndcCoords + * @param {THREE.Vector2} ndcCoords * @return {THREE.Vector2} - view coordinates (in pixels, 0-0 = top-left of the View) */ View.prototype.normalizedToViewCoords = function normalizedToViewCoords(ndcCoords) { @@ -664,7 +689,7 @@ View.prototype.pickObjectsAt = function pickObjectsAt(mouseOrEvt, radius, ...whe } } } - } else if (source instanceof Object3D) { + } else if (source instanceof THREE.Object3D) { Picking.pickObjectsAt( this, mouse, diff --git a/src/Layer/ColorLayer.js b/src/Layer/ColorLayer.js new file mode 100644 index 0000000000..b3e8cc896d --- /dev/null +++ b/src/Layer/ColorLayer.js @@ -0,0 +1,14 @@ +import Layer from './Layer'; + +class ColorLayer extends Layer { + constructor(id) { + super(id, 'color'); + + this.defineLayerProperty('frozen', false); + this.defineLayerProperty('visible', true); + this.defineLayerProperty('opacity', 1.0); + this.defineLayerProperty('sequence', 0); + } +} + +export default ColorLayer; diff --git a/src/Layer/ElevationLayer.js b/src/Layer/ElevationLayer.js new file mode 100644 index 0000000000..1dab372131 --- /dev/null +++ b/src/Layer/ElevationLayer.js @@ -0,0 +1,11 @@ +import Layer from './Layer'; + +class ElevationLayer extends Layer { + constructor(id) { + super(id, 'elevation'); + + this.defineLayerProperty('frozen', false); + } +} + +export default ElevationLayer; diff --git a/src/Layer/GeometryLayer.js b/src/Layer/GeometryLayer.js new file mode 100644 index 0000000000..ac8eeed700 --- /dev/null +++ b/src/Layer/GeometryLayer.js @@ -0,0 +1,42 @@ +import Layer from './Layer'; +import Picking from '../Core/Picking'; + +class GeometryLayer extends Layer { + constructor(id, object3d) { + if (!object3d || !object3d.isObject3D) { + throw new Error('Missing/Invalid object3d parameter (must be a three.js Object3D instance)'); + } + + super(id, 'geometry'); + + this._attachedLayers = []; + if (object3d.type === 'Group' && object3d.name === '') { + object3d.name = id; + } + + Object.defineProperty(this, 'object3d', { + value: object3d, + writable: false, + }); + + // Setup default picking method + this.pickObjectsAt = (view, mouse, radius) => Picking.pickObjectsAt(view, mouse, radius, this.object3d); + + this.postUpdate = () => {}; + } + + attach(layer) { + if (!layer.update) { + throw new Error(`Missing 'update' function -> can't attach layer ${layer.id}`); + } + this._attachedLayers.push(layer); + } + + detach(layer) { + const count = this._attachedLayers.length; + this._attachedLayers = this._attachedLayers.filter(attached => attached.id != layer.id); + return this._attachedLayers.length < count; + } +} + +export default GeometryLayer; diff --git a/src/Core/Layer/Layer.js b/src/Layer/Layer.js similarity index 55% rename from src/Core/Layer/Layer.js rename to src/Layer/Layer.js index 8518110152..b6f3c1de46 100644 --- a/src/Core/Layer/Layer.js +++ b/src/Layer/Layer.js @@ -1,4 +1,4 @@ -import { EventDispatcher } from 'three'; +import * as THREE from 'three'; /** * Fires when layer sequence change (meaning when the order of the layer changes in the view) @@ -31,64 +31,77 @@ import { EventDispatcher } from 'three'; * @property type {string} visible-property-changed */ -export const defineLayerProperty = function defineLayerProperty(layer, propertyName, defaultValue, onChange) { - const existing = Object.getOwnPropertyDescriptor(layer, propertyName); - if (!existing || !existing.set) { - var property = layer[propertyName] == undefined ? defaultValue : layer[propertyName]; - Object.defineProperty(layer, - propertyName, - { get: () => property, - set: (newValue) => { - if (property !== newValue) { - const event = { type: `${propertyName}-property-changed`, previous: {}, new: {} }; - event.previous[propertyName] = property; - event.new[propertyName] = newValue; - property = newValue; - if (onChange) { - onChange(layer, propertyName); - } - layer.dispatchEvent(event); - } - } }); +class Layer extends THREE.EventDispatcher { + /** + * Don't use directly constructor to instance a new Layer + * use addLayer in {@link View} + * @example + * // add and create a new Layer + * const newLayer = view.addLayer({options}); + * + * // Change layer's visibilty + * const layerToChange = view.getLayers(layer => layer.id == 'idLayerToChange')[0]; + * layerToChange.visible = false; + * view.notifyChange(); // update viewer + * + * // Change layer's opacity + * const layerToChange = view.getLayers(layer => layer.id == 'idLayerToChange')[0]; + * layerToChange.opacity = 0.5; + * view.notifyChange(); // update viewer + * + * // Listen properties + * const layerToListen = view.getLayers(layer => layer.id == 'idLayerToListen')[0]; + * layerToListen.addEventListener('visible-property-changed', (event) => console.log(event)); + * layerToListen.addEventListener('opacity-property-changed', (event) => console.log(event)); + * @constructor + * @protected + * @param {string} id + * @param {string} type + */ + constructor(id, type) { + super(); + + Object.defineProperty(this, 'id', { + value: id, + writable: false, + }); + + Object.defineProperty(this, 'type', { + value: type, + writable: false, + }); } -}; -/** - * Don't use directly constructor to instance a new Layer - * use addLayer in {@link View} - * @example - * // add and create a new Layer - * const newLayer = view.addLayer({options}); - * - * // Change layer's visibilty - * const layerToChange = view.getLayers(layer => layer.id == 'idLayerToChange')[0]; - * layerToChange.visible = false; - * view.notifyChange(); // update viewer - * - * // Change layer's opacity - * const layerToChange = view.getLayers(layer => layer.id == 'idLayerToChange')[0]; - * layerToChange.opacity = 0.5; - * view.notifyChange(); // update viewer - * - * // Listen properties - * const layerToListen = view.getLayers(layer => layer.id == 'idLayerToListen')[0]; - * layerToListen.addEventListener('visible-property-changed', (event) => console.log(event)); - * layerToListen.addEventListener('opacity-property-changed', (event) => console.log(event)); - * @constructor - * @protected - * @param {String} id - */ -function Layer(id) { - Object.defineProperty(this, 'id', { - value: id, - writable: false, - }); + defineLayerProperty(propertyName, defaultValue, onChange) { + const existing = Object.getOwnPropertyDescriptor(this, propertyName); + if (!existing || !existing.set) { + let property = this[propertyName] == undefined ? defaultValue : this[propertyName]; + + Object.defineProperty( + this, + propertyName, + { + get: () => property, + set: (newValue) => { + if (property !== newValue) { + const event = { type: `${propertyName}-property-changed`, previous: {}, new: {} }; + event.previous[propertyName] = property; + event.new[propertyName] = newValue; + property = newValue; + if (onChange) { + onChange(this, propertyName); + } + this.dispatchEvent(event); + } + }, + }); + } + } } -Layer.prototype = Object.create(EventDispatcher.prototype); -Layer.prototype.constructor = Layer; +export default Layer; -const ImageryLayers = { +export const ImageryLayers = { // move layer to new index // After the modification : // * the minimum sequence will always be 0 @@ -133,4 +146,3 @@ const ImageryLayers = { }, }; -export { Layer, ImageryLayers }; diff --git a/src/Core/Layer/LayerUpdateState.js b/src/Layer/LayerUpdateState.js similarity index 100% rename from src/Core/Layer/LayerUpdateState.js rename to src/Layer/LayerUpdateState.js diff --git a/src/Core/Layer/LayerUpdateStrategy.js b/src/Layer/LayerUpdateStrategy.js similarity index 97% rename from src/Core/Layer/LayerUpdateStrategy.js rename to src/Layer/LayerUpdateStrategy.js index c4a5b43e04..32c77291d9 100644 --- a/src/Core/Layer/LayerUpdateStrategy.js +++ b/src/Layer/LayerUpdateStrategy.js @@ -1,4 +1,4 @@ -import { EMPTY_TEXTURE_ZOOM } from '../../Renderer/LayeredMaterialConstants'; +import { EMPTY_TEXTURE_ZOOM } from '../Renderer/LayeredMaterialConstants'; /** * This modules implements various layer update strategies. * diff --git a/src/Layer/TiledGeometryLayer.js b/src/Layer/TiledGeometryLayer.js new file mode 100644 index 0000000000..d4bb98d35e --- /dev/null +++ b/src/Layer/TiledGeometryLayer.js @@ -0,0 +1,63 @@ +import GeometryLayer from './GeometryLayer'; + +class TiledGeometryLayer extends GeometryLayer { + constructor(id, object3d) { + super(id, object3d); + + this.protocol = 'tile'; + this.visible = true; + this.lighting = { + enable: false, + position: { x: -0.5, y: 0.0, z: 1.0 }, + }; + } + + preUpdate(context, changeSources) { + let commonAncestor; + for (const source of changeSources.values()) { + if (source.isCamera) { + // if the change is caused by a camera move, no need to bother + // to find common ancestor: we need to update the whole tree: + // some invisible tiles may now be visible + return this.level0Nodes; + } + if (source.this === this) { + if (!commonAncestor) { + commonAncestor = source; + } else { + commonAncestor = source.findCommonAncestor(commonAncestor); + if (!commonAncestor) { + return this.level0Nodes; + } + } + if (commonAncestor.material == null) { + commonAncestor = undefined; + } + } + } + if (commonAncestor) { + if (__DEBUG__) { + this._latestUpdateStartingLevel = commonAncestor.level; + } + return [commonAncestor]; + } else { + return this.level0Nodes; + } + } + + onTileCreated(node) { + node.material.setLightingOn(this.lighting.enable); + node.material.uniforms.lightPosition.value = this.lighting.position; + + if (this.noTextureColor) { + node.material.uniforms.noTextureColor.value.copy(this.noTextureColor); + } + + if (__DEBUG__) { + node.material.uniforms.showOutline = { value: this.showOutline || false }; + node.material.wireframe = this.wireframe || false; + } + } +} + +export default TiledGeometryLayer; diff --git a/src/Main.js b/src/Main.js index 4a6542cc54..a68bcab255 100644 --- a/src/Main.js +++ b/src/Main.js @@ -1,8 +1,8 @@ export { default as Coordinates, UNIT } from './Core/Geographic/Coordinates'; export { default as Extent } from './Core/Geographic/Extent'; -export { ImageryLayers } from './Core/Layer/Layer'; -export { default as GeometryLayer } from './Core/Layer/GeometryLayer'; -export { STRATEGY_MIN_NETWORK_TRAFFIC, STRATEGY_GROUP, STRATEGY_PROGRESSIVE, STRATEGY_DICHOTOMY } from './Core/Layer/LayerUpdateStrategy'; +export { ImageryLayers } from './Layer/Layer'; +export { default as GeometryLayer } from './Layer/GeometryLayer'; +export { STRATEGY_MIN_NETWORK_TRAFFIC, STRATEGY_GROUP, STRATEGY_PROGRESSIVE, STRATEGY_DICHOTOMY } from './Layer/LayerUpdateStrategy'; export { default as GlobeView, GLOBE_VIEW_EVENTS, createGlobeLayer } from './Core/Prefab/GlobeView'; export { default as PlanarView, createPlanarLayer } from './Core/Prefab/PlanarView'; export { default as PanoramaView, createPanoramaLayer } from './Core/Prefab/PanoramaView'; diff --git a/src/Process/FeatureProcessing.js b/src/Process/FeatureProcessing.js index d9f9f05d5b..ebe2a5b1fa 100644 --- a/src/Process/FeatureProcessing.js +++ b/src/Process/FeatureProcessing.js @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import LayerUpdateState from '../Core/Layer/LayerUpdateState'; +import LayerUpdateState from '../Layer/LayerUpdateState'; import CancelledCommandException from '../Core/Scheduler/CancelledCommandException'; import ObjectRemovalHelper from './ObjectRemovalHelper'; diff --git a/src/Process/LayeredMaterialNodeProcessing.js b/src/Process/LayeredMaterialNodeProcessing.js index 9af513f9c5..93c41ec0b2 100644 --- a/src/Process/LayeredMaterialNodeProcessing.js +++ b/src/Process/LayeredMaterialNodeProcessing.js @@ -1,7 +1,7 @@ import { l_ELEVATION, l_COLOR, EMPTY_TEXTURE_ZOOM } from '../Renderer/LayeredMaterialConstants'; -import { chooseNextLevelToFetch } from '../Core/Layer/LayerUpdateStrategy'; -import LayerUpdateState from '../Core/Layer/LayerUpdateState'; -import { ImageryLayers } from '../Core/Layer/Layer'; +import { chooseNextLevelToFetch } from '../Layer/LayerUpdateStrategy'; +import LayerUpdateState from '../Layer/LayerUpdateState'; +import { ImageryLayers } from '../Layer/Layer'; import CancelledCommandException from '../Core/Scheduler/CancelledCommandException'; import { SIZE_TEXTURE_TILE } from '../Provider/OGCWebServiceHelper'; import { computeMinMaxElevation } from '../Parser/XbilParser'; diff --git a/src/Provider/PointCloudProvider.js b/src/Provider/PointCloudProvider.js index 20601899cd..96b32a2505 100644 --- a/src/Provider/PointCloudProvider.js +++ b/src/Provider/PointCloudProvider.js @@ -165,7 +165,6 @@ export default { layer.pointBudget = layer.pointBudget || 2000000; layer.pointSize = layer.pointSize === 0 || !isNaN(layer.pointSize) ? layer.pointSize : 4; layer.sseThreshold = layer.sseThreshold || 2; - layer.type = 'geometry'; layer.material = layer.material || {}; layer.material = layer.material.isMaterial ? layer.material : new PointsMaterial(layer.material); diff --git a/src/Renderer/ColorLayersOrdering.js b/src/Renderer/ColorLayersOrdering.js index bca62979fa..0df3348fff 100644 --- a/src/Renderer/ColorLayersOrdering.js +++ b/src/Renderer/ColorLayersOrdering.js @@ -1,4 +1,4 @@ -import { ImageryLayers } from '../Core/Layer/Layer'; +import { ImageryLayers } from '../Layer/Layer'; function updateLayersOrdering(geometryLayer, imageryLayers) { var sequence = ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers);