From 8a9da2356636fca46c355fa15890c0a0e424223c Mon Sep 17 00:00:00 2001 From: Robin Hawkes Date: Wed, 7 Sep 2016 16:01:02 +0100 Subject: [PATCH 1/4] Dedupe worker tile layers --- examples/web-workers-basic/main.js | 3 +- src/layer/tile/GeoJSONTile.js | 40 ++- src/layer/tile/GeoJSONTileLayer.js | 35 +-- src/layer/tile/GeoJSONWorkerTile.js | 341 ---------------------- src/layer/tile/GeoJSONWorkerTileLayer.js | 158 ---------- src/layer/tile/TopoJSONWorkerTileLayer.js | 22 -- src/vizicities.js | 6 - 7 files changed, 38 insertions(+), 567 deletions(-) delete mode 100644 src/layer/tile/GeoJSONWorkerTile.js delete mode 100644 src/layer/tile/GeoJSONWorkerTileLayer.js delete mode 100644 src/layer/tile/TopoJSONWorkerTileLayer.js diff --git a/examples/web-workers-basic/main.js b/examples/web-workers-basic/main.js index eb2f2cd..4fa21c8 100644 --- a/examples/web-workers-basic/main.js +++ b/examples/web-workers-basic/main.js @@ -21,7 +21,8 @@ world.createWorkers(7).then(() => { });; // Buildings from Mapzen - VIZI.topoJSONWorkerTileLayer('https://vector.mapzen.com/osm/buildings/{z}/{x}/{y}.topojson?api_key=vector-tiles-NT5Emiw', { + VIZI.topoJSONTileLayer('https://vector.mapzen.com/osm/buildings/{z}/{x}/{y}.topojson?api_key=vector-tiles-NT5Emiw', { + workers: true, interactive: false, style: function(feature) { var height; diff --git a/src/layer/tile/GeoJSONTile.js b/src/layer/tile/GeoJSONTile.js index 70cf946..d01dc4e 100644 --- a/src/layer/tile/GeoJSONTile.js +++ b/src/layer/tile/GeoJSONTile.js @@ -1,5 +1,6 @@ import Tile from './Tile'; import {geoJSONLayer as GeoJSONLayer} from '../GeoJSONLayer'; +import {geoJSONWorkerLayer as GeoJSONWorkerLayer} from '../GeoJSONWorkerLayer'; import BoxHelper from '../../vendor/BoxHelper'; import THREE from 'three'; import reqwest from 'reqwest'; @@ -51,6 +52,7 @@ class GeoJSONTile extends Tile { this._defaultStyle = GeoJSON.defaultStyle; var defaults = { + workers: false, output: true, outputToScene: false, interactive: false, @@ -231,27 +233,33 @@ class GeoJSONTile extends Tile { var url = this._getTileURL(urlParams); - this._request = reqwest({ - url: url, - type: 'json', - crossOrigin: true - }).then(res => { - // Clear request reference - this._request = null; - this._processTileData(res); - }).catch(err => { - console.error(err); + if (!this._options.workers) { + this._request = reqwest({ + url: url, + type: 'json', + crossOrigin: true + }).then(res => { + // Clear request reference + this._request = null; + this._processTileData(res); + }).catch(err => { + console.error(err); - // Clear request reference - this._request = null; - }); + // Clear request reference + this._request = null; + }); + } else { + this._processTileData(url); + } } _processTileData(data) { console.time(this._tile); + var GeoJSONClass = (!this._options.workers) ? GeoJSONLayer : GeoJSONWorkerLayer; + // Using this creates a huge amount of memory due to the quantity of tiles - this._geojsonLayer = GeoJSONLayer(data, this._options); + this._geojsonLayer = GeoJSONClass(data, this._options); this._geojsonLayer.addTo(this._world).then(() => { this._mesh = this._geojsonLayer._object3D; this._pickingMesh = this._geojsonLayer._pickingMesh; @@ -259,7 +267,9 @@ class GeoJSONTile extends Tile { // Free the GeoJSON memory as we don't need it // // TODO: This should probably be a method within GeoJSONLayer - this._geojsonLayer._geojson = null; + if (this._geojsonLayer._geojson) { + this._geojsonLayer._geojson = null; + } // TODO: Fix or store shadow canvas stuff and get rid of this code // Draw footprint on shadow canvas diff --git a/src/layer/tile/GeoJSONTileLayer.js b/src/layer/tile/GeoJSONTileLayer.js index 4a78dd7..af4119b 100644 --- a/src/layer/tile/GeoJSONTileLayer.js +++ b/src/layer/tile/GeoJSONTileLayer.js @@ -38,7 +38,8 @@ class GeoJSONTileLayer extends TileLayer { constructor(path, options) { var defaults = { maxLOD: 14, - distance: 30000 + distance: 30000, + workers: false }; options = extend({}, defaults, options); @@ -78,7 +79,7 @@ class GeoJSONTileLayer extends TileLayer { // Update and output tiles each frame (throttled) _onWorldUpdate() { - if (this._pauseOutput) { + if (this._pauseOutput || this._disableOutput) { return; } @@ -87,38 +88,24 @@ class GeoJSONTileLayer extends TileLayer { // Update tiles grid after world move, but don't output them _onWorldMove(latlon, point) { + if (this._disableOutput) { + return; + } + this._pauseOutput = false; this._calculateLOD(); } // Pause updates during control movement for less visual jank _onControlsMove() { + if (this._disableOutput) { + return; + } + this._pauseOutput = true; } _createTile(quadcode, layer) { - var options = {}; - - // if (this._options.filter) { - // options.filter = this._options.filter; - // } - // - // if (this._options.style) { - // options.style = this._options.style; - // } - // - // if (this._options.topojson) { - // options.topojson = true; - // } - // - // if (this._options.interactive) { - // options.interactive = true; - // } - // - // if (this._options.onClick) { - // options.onClick = this._options.onClick; - // } - return new GeoJSONTile(quadcode, this._path, layer, this._options); } diff --git a/src/layer/tile/GeoJSONWorkerTile.js b/src/layer/tile/GeoJSONWorkerTile.js deleted file mode 100644 index 40b0c0f..0000000 --- a/src/layer/tile/GeoJSONWorkerTile.js +++ /dev/null @@ -1,341 +0,0 @@ -import Tile from './Tile'; -import {geoJSONWorkerLayer as GeoJSONWorkerLayer} from '../GeoJSONWorkerLayer'; -import BoxHelper from '../../vendor/BoxHelper'; -import THREE from 'three'; -import reqwest from 'reqwest'; -import {point as Point} from '../../geo/Point'; -import {latLon as LatLon} from '../../geo/LatLon'; -import extend from 'lodash.assign'; -// import Offset from 'polygon-offset'; -import GeoJSON from '../../util/GeoJSON'; -import Buffer from '../../util/Buffer'; -import PickingMaterial from '../../engine/PickingMaterial'; - -// TODO: Map picking IDs to some reference within the tile data / geometry so -// that something useful can be done when an object is picked / clicked on - -// TODO: Make sure nothing is left behind in the heap after calling destroy() - -// TODO: Perform tile request and processing in a Web Worker -// -// Use Operative (https://github.com/padolsey/operative) -// -// Would it make sense to have the worker functionality defined in a static -// method so it only gets initialised once and not on every tile instance? -// -// Otherwise, worker processing logic would have to go in the tile layer so not -// to waste loads of time setting up a brand new worker with three.js for each -// tile every single time. -// -// Unsure of the best way to get three.js and VIZI into the worker -// -// Would need to set up a CRS / projection identical to the world instance -// -// Is it possible to bypass requirements on external script by having multiple -// simple worker methods that each take enough inputs to perform a single task -// without requiring VIZI or three.js? So long as the heaviest logic is done in -// the worker and transferrable objects are used then it should be better than -// nothing. Would probably still need things like earcut... -// -// After all, the three.js logic and object creation will still need to be -// done on the main thread regardless so the worker should try to do as much as -// possible with as few dependencies as possible. -// -// Have a look at how this is done in Tangram before implementing anything as -// the approach there is pretty similar and robust. - -class GeoJSONWorkerTile extends Tile { - constructor(quadcode, path, layer, options) { - super(quadcode, path, layer); - - this._defaultStyle = GeoJSON.defaultStyle; - - var defaults = { - output: true, - outputToScene: false, - interactive: false, - topojson: false, - filter: null, - onEachFeature: null, - polygonMaterial: null, - onPolygonMesh: null, - onPolygonBufferAttributes: null, - polylineMaterial: null, - onPolylineMesh: null, - onPolylineBufferAttributes: null, - pointGeometry: null, - pointMaterial: null, - onPointMesh: null, - style: GeoJSON.defaultStyle, - keepFeatures: false - }; - - var _options = extend({}, defaults, options); - - if (typeof options.style === 'function') { - _options.style = options.style; - } else { - _options.style = extend({}, defaults.style, options.style); - } - - this._options = _options; - } - - // Request data for the tile - requestTileAsync() { - // Making this asynchronous really speeds up the LOD framerate - setTimeout(() => { - if (!this._mesh) { - this._mesh = this._createMesh(); - - // this._shadowCanvas = this._createShadowCanvas(); - - this._requestTile(); - } - }, 0); - } - - // TODO: Destroy GeoJSONLayer - destroy() { - // Cancel any pending requests - this._abortRequest(); - - // Clear request reference - this._request = null; - - if (this._geojsonLayer) { - this._geojsonLayer.destroy(); - this._geojsonLayer = null; - } - - this._mesh = null; - - // TODO: Properly dispose of picking mesh - this._pickingMesh = null; - - super.destroy(); - } - - _createMesh() { - // Something went wrong and the tile - // - // Possibly removed by the cache before loaded - if (!this._center) { - return; - } - - var mesh = new THREE.Object3D(); - // mesh.add(this._createDebugMesh()); - - return mesh; - } - - _createDebugMesh() { - var canvas = document.createElement('canvas'); - canvas.width = 256; - canvas.height = 256; - - var context = canvas.getContext('2d'); - context.font = 'Bold 20px Helvetica Neue, Verdana, Arial'; - context.fillStyle = '#ff0000'; - context.fillText(this._quadcode, 20, canvas.width / 2 - 5); - context.fillText(this._tile.toString(), 20, canvas.width / 2 + 25); - - var texture = new THREE.Texture(canvas); - - // Silky smooth images when tilted - texture.magFilter = THREE.LinearFilter; - texture.minFilter = THREE.LinearMipMapLinearFilter; - - // TODO: Set this to renderer.getMaxAnisotropy() / 4 - texture.anisotropy = 4; - - texture.needsUpdate = true; - - var material = new THREE.MeshBasicMaterial({ - map: texture, - transparent: true, - depthWrite: false - }); - - var geom = new THREE.PlaneBufferGeometry(this._side, this._side, 1); - var mesh = new THREE.Mesh(geom, material); - - mesh.rotation.x = -90 * Math.PI / 180; - mesh.position.y = 0.1; - - return mesh; - } - - // _createShadowCanvas() { - // var canvas = document.createElement('canvas'); - // - // // Rendered at a low resolution and later scaled up for a low-quality blur - // canvas.width = 512; - // canvas.height = 512; - // - // return canvas; - // } - - // _addShadow(coordinates) { - // var ctx = this._shadowCanvas.getContext('2d'); - // var width = this._shadowCanvas.width; - // var height = this._shadowCanvas.height; - // - // var _coords; - // var _offset; - // var offset = new Offset(); - // - // // Transform coordinates to shadowCanvas space and draw on canvas - // coordinates.forEach((ring, index) => { - // ctx.beginPath(); - // - // _coords = ring.map(coord => { - // var xFrac = (coord[0] - this._boundsWorld[0]) / this._side; - // var yFrac = (coord[1] - this._boundsWorld[3]) / this._side; - // return [xFrac * width, yFrac * height]; - // }); - // - // if (index > 0) { - // _offset = _coords; - // } else { - // _offset = offset.data(_coords).padding(1.3); - // } - // - // // TODO: This is super flaky and crashes the browser if run on anything - // // put the outer ring (potentially due to winding) - // _offset.forEach((coord, index) => { - // // var xFrac = (coord[0] - this._boundsWorld[0]) / this._side; - // // var yFrac = (coord[1] - this._boundsWorld[3]) / this._side; - // - // if (index === 0) { - // ctx.moveTo(coord[0], coord[1]); - // } else { - // ctx.lineTo(coord[0], coord[1]); - // } - // }); - // - // ctx.closePath(); - // }); - // - // ctx.fillStyle = 'rgba(80, 80, 80, 0.7)'; - // ctx.fill(); - // } - - _requestTile() { - var urlParams = { - x: this._tile[0], - y: this._tile[1], - z: this._tile[2] - }; - - var url = this._getTileURL(urlParams); - - // this._request = reqwest({ - // url: url, - // type: 'json', - // crossOrigin: true - // }).then(res => { - // // Clear request reference - // this._request = null; - // this._processTileData(res); - // }).catch(err => { - // console.error(err); - - // // Clear request reference - // this._request = null; - // }); - - this._processTileData(url); - } - - _processTileData(data) { - console.time(this._tile); - - // Using this creates a huge amount of memory due to the quantity of tiles - var geojsonLayer = GeoJSONWorkerLayer(data, this._options); - geojsonLayer.addTo(this._world).then(() => { - this._geojsonLayer = geojsonLayer; - this._mesh = this._geojsonLayer._object3D; - this._pickingMesh = this._geojsonLayer._pickingMesh; - - // Free the GeoJSON memory as we don't need it - // - // TODO: This should probably be a method within GeoJSONLayer - // WORKERS: Disabled for now as it's needed with sync promises - // this._geojsonLayer._geojson = null; - - // TODO: Fix or store shadow canvas stuff and get rid of this code - // Draw footprint on shadow canvas - // - // TODO: Disabled for the time-being until it can be sped up / moved to - // a worker - // this._addShadow(coordinates); - - // Output shadow canvas - - // TODO: Disabled for the time-being until it can be sped up / moved to - // a worker - - // var texture = new THREE.Texture(this._shadowCanvas); - // - // // Silky smooth images when tilted - // texture.magFilter = THREE.LinearFilter; - // texture.minFilter = THREE.LinearMipMapLinearFilter; - // - // // TODO: Set this to renderer.getMaxAnisotropy() / 4 - // texture.anisotropy = 4; - // - // texture.needsUpdate = true; - // - // var material; - // if (!this._world._environment._skybox) { - // material = new THREE.MeshBasicMaterial({ - // map: texture, - // transparent: true, - // depthWrite: false - // }); - // } else { - // material = new THREE.MeshStandardMaterial({ - // map: texture, - // transparent: true, - // depthWrite: false - // }); - // material.roughness = 1; - // material.metalness = 0.1; - // material.envMap = this._world._environment._skybox.getRenderTarget(); - // } - // - // var geom = new THREE.PlaneBufferGeometry(this._side, this._side, 1); - // var mesh = new THREE.Mesh(geom, material); - // - // mesh.castShadow = false; - // mesh.receiveShadow = false; - // mesh.renderOrder = 1; - // - // mesh.rotation.x = -90 * Math.PI / 180; - // - // this._mesh.add(mesh); - - this._ready = true; - console.timeEnd(this._tile); - }); - } - - _abortRequest() { - if (!this._request) { - return; - } - - this._request.abort(); - } -} - -export default GeoJSONWorkerTile; - -var noNew = function(quadcode, path, layer, options) { - return new GeoJSONWorkerTile(quadcode, path, layer, options); -}; - -// Initialise without requiring new keyword -export {noNew as geoJSONWorkerTile}; diff --git a/src/layer/tile/GeoJSONWorkerTileLayer.js b/src/layer/tile/GeoJSONWorkerTileLayer.js deleted file mode 100644 index 1e8b198..0000000 --- a/src/layer/tile/GeoJSONWorkerTileLayer.js +++ /dev/null @@ -1,158 +0,0 @@ -import TileLayer from './TileLayer'; -import extend from 'lodash.assign'; -import GeoJSONWorkerTile from './GeoJSONWorkerTile'; -import throttle from 'lodash.throttle'; -import THREE from 'three'; - -// TODO: Offer on-the-fly slicing of static, non-tile-based GeoJSON files into a -// tile grid using geojson-vt -// -// See: https://github.com/mapbox/geojson-vt - -// TODO: Make sure nothing is left behind in the heap after calling destroy() - -// TODO: Consider pausing per-frame output during movement so there's little to -// no jank caused by previous tiles still processing - -// This tile layer only updates the quadtree after world movement has occurred -// -// Tiles from previous quadtree updates are updated and outputted every frame -// (or at least every frame, throttled to some amount) -// -// This is because the complexity of TopoJSON tiles requires a lot of processing -// and so makes movement janky if updates occur every frame – only updating -// after movement means frame drops are less obvious due to heavy processing -// occurring while the view is generally stationary -// -// The downside is that until new tiles are requested and outputted you will -// see blank spaces as you orbit and move around -// -// An added benefit is that it dramatically reduces the number of tiles being -// requested over a period of time and the time it takes to go from request to -// screen output -// -// It may be possible to perform these updates per-frame once Web Worker -// processing is added - -class GeoJSONWorkerTileLayer extends TileLayer { - constructor(path, options) { - var defaults = { - maxLOD: 14, - distance: 30000 - }; - - options = extend({}, defaults, options); - - super(options); - - this._path = path; - } - - _onAdd(world) { - return super._onAdd(world).then(() => { - // Trigger initial quadtree calculation on the next frame - // - // TODO: This is a hack to ensure the camera is all set up - a better - // solution should be found - setTimeout(() => { - this._calculateLOD(); - this._initEvents(); - }, 0); - }); - } - - _initEvents() { - // Run LOD calculations based on render calls - // - // Throttled to 1 LOD calculation per 100ms - this._throttledWorldUpdate = throttle(this._onWorldUpdate, 100); - - this._world.on('preUpdate', this._throttledWorldUpdate, this); - this._world.on('move', this._onWorldMove, this); - this._world.on('controlsMove', this._onControlsMove, this); - } - - // Update and output tiles each frame (throttled) - _onWorldUpdate() { - if (this._pauseOutput || this._disableOutput) { - return; - } - - this._outputTiles(); - } - - // Update tiles grid after world move, but don't output them - _onWorldMove(latlon, point) { - if (this._disableOutput) { - return; - } - - this._pauseOutput = false; - this._calculateLOD(); - } - - // Pause updates during control movement for less visual jank - _onControlsMove() { - if (this._disableOutput) { - return; - } - - this._pauseOutput = true; - } - - _createTile(quadcode, layer) { - var options = {}; - - // if (this._options.filter) { - // options.filter = this._options.filter; - // } - // - // if (this._options.style) { - // options.style = this._options.style; - // } - // - // if (this._options.topojson) { - // options.topojson = true; - // } - // - // if (this._options.interactive) { - // options.interactive = true; - // } - // - // if (this._options.onClick) { - // options.onClick = this._options.onClick; - // } - - var workerTile = new GeoJSONWorkerTile(quadcode, this._path, layer, this._options); - - // workerTile.on('click', (properties, point2d, point3d, intersects) => { - // console.log(properties, point2d, point3d, intersects); - // }); - - // workerTile.on('hover', (properties, point2d, point3d, intersects) => { - - // }); - - return workerTile; - } - - // Destroys the layer and removes it from the scene and memory - destroy() { - this._world.off('preUpdate', this._throttledWorldUpdate); - this._world.off('move', this._onWorldMove); - - this._throttledWorldUpdate = null; - - // Run common destruction logic from parent - super.destroy(); - } -} - -export default GeoJSONWorkerTileLayer; - -var noNew = function(path, options) { - return new GeoJSONWorkerTileLayer(path, options); -}; - -// Initialise without requiring new keyword -export {noNew as geoJSONWorkerTileLayer}; diff --git a/src/layer/tile/TopoJSONWorkerTileLayer.js b/src/layer/tile/TopoJSONWorkerTileLayer.js deleted file mode 100644 index c8c9326..0000000 --- a/src/layer/tile/TopoJSONWorkerTileLayer.js +++ /dev/null @@ -1,22 +0,0 @@ -import GeoJSONWorkerTileLayer from './GeoJSONWorkerTileLayer'; -import extend from 'lodash.assign'; - -class TopoJSONWorkerTileLayer extends GeoJSONWorkerTileLayer { - constructor(path, options) { - var defaults = { - topojson: true - }; - - options = extend({}, defaults, options); - - super(path, options); - } -} - -export default TopoJSONWorkerTileLayer; - -var noNew = function(path, options) { - return new TopoJSONWorkerTileLayer(path, options); -}; - -export {noNew as topoJSONWorkerTileLayer}; diff --git a/src/vizicities.js b/src/vizicities.js index df87945..876c88d 100644 --- a/src/vizicities.js +++ b/src/vizicities.js @@ -8,8 +8,6 @@ import EnvironmentLayer, {environmentLayer} from './layer/environment/Environmen import ImageTileLayer, {imageTileLayer} from './layer/tile/ImageTileLayer'; import GeoJSONTileLayer, {geoJSONTileLayer} from './layer/tile/GeoJSONTileLayer'; import TopoJSONTileLayer, {topoJSONTileLayer} from './layer/tile/TopoJSONTileLayer'; -import GeoJSONWorkerTileLayer, {geoJSONWorkerTileLayer} from './layer/tile/GeoJSONWorkerTileLayer'; -import TopoJSONWorkerTileLayer, {topoJSONWorkerTileLayer} from './layer/tile/TopoJSONWorkerTileLayer'; import GeoJSONLayer, {geoJSONLayer} from './layer/GeoJSONLayer'; import TopoJSONLayer, {topoJSONLayer} from './layer/TopoJSONLayer'; import GeoJSONWorkerLayer, {geoJSONWorkerLayer} from './layer/GeoJSONWorkerLayer'; @@ -43,10 +41,6 @@ const VIZI = { geoJSONTileLayer: geoJSONTileLayer, TopoJSONTileLayer: TopoJSONTileLayer, topoJSONTileLayer: topoJSONTileLayer, - GeoJSONWorkerTileLayer: GeoJSONWorkerTileLayer, - geoJSONWorkerTileLayer: geoJSONWorkerTileLayer, - TopoJSONWorkerTileLayer: TopoJSONWorkerTileLayer, - topoJSONWorkerTileLayer: topoJSONWorkerTileLayer, GeoJSONLayer: GeoJSONLayer, geoJSONLayer: geoJSONLayer, TopoJSONLayer: TopoJSONLayer, From 86fc1f6d5d4d10a7aa0a9a03c438f6069275672c Mon Sep 17 00:00:00 2001 From: Robin Hawkes Date: Thu, 8 Sep 2016 14:02:55 +0100 Subject: [PATCH 2/4] Deduped mesh creation using static methods --- src/layer/GeoJSONLayer.js | 304 +++++++++++--------------- src/layer/GeoJSONWorkerLayer.js | 150 +++---------- src/layer/geometry/PolygonLayer.js | 331 ++++++++++++++--------------- 3 files changed, 314 insertions(+), 471 deletions(-) diff --git a/src/layer/GeoJSONLayer.js b/src/layer/GeoJSONLayer.js index 2244f78..3ce1e84 100644 --- a/src/layer/GeoJSONLayer.js +++ b/src/layer/GeoJSONLayer.js @@ -98,197 +98,153 @@ class GeoJSONLayer extends LayerGroup { // Need to be careful as to not make it impossible to fork this off into a // worker script at a later stage _processData(data) { - // Collects features into a single FeatureCollection - // - // Also converts TopoJSON to GeoJSON if instructed - this._geojson = GeoJSON.collectFeatures(data, this._options.topojson); + return new Promise((resolve) => { + // Collects features into a single FeatureCollection + // + // Also converts TopoJSON to GeoJSON if instructed + this._geojson = GeoJSON.collectFeatures(data, this._options.topojson); - // TODO: Check that GeoJSON is valid / usable + // TODO: Check that GeoJSON is valid / usable - var features = this._geojson.features; + var features = this._geojson.features; - // Run filter, if provided - if (this._options.filter) { - features = this._geojson.features.filter(this._options.filter); - } - - var defaults = {}; - - // Assume that a style won't be set per feature - var style = this._options.style; - - var options; - features.forEach(feature => { - // Get per-feature style object, if provided - if (typeof this._options.style === 'function') { - style = extend({}, GeoJSON.defaultStyle, this._options.style(feature)); + // Run filter, if provided + if (this._options.filter) { + features = this._geojson.features.filter(this._options.filter); } - options = extend({}, defaults, { - // If merging feature layers, stop them outputting themselves - // If not, let feature layers output themselves to the world - output: !this.isOutput(), - interactive: this._options.interactive, - style: style + var defaults = {}; + + // Assume that a style won't be set per feature + var style = this._options.style; + + var layerPromises = []; + + var options; + features.forEach(feature => { + // Get per-feature style object, if provided + if (typeof this._options.style === 'function') { + style = extend({}, GeoJSON.defaultStyle, this._options.style(feature)); + } + + options = extend({}, defaults, { + // If merging feature layers, stop them outputting themselves + // If not, let feature layers output themselves to the world + output: !this.isOutput(), + interactive: this._options.interactive, + style: style + }); + + var layer = this._featureToLayer(feature, options); + + if (!layer) { + return; + } + + // Sometimes you don't want to store a reference to the feature + // + // For example, to save memory when being used by tile layers + if (this._options.keepFeatures) { + layer.feature = feature; + } + + // If defined, call a function for each feature + // + // This is commonly used for adding event listeners from the user script + if (this._options.onEachFeature) { + this._options.onEachFeature(feature, layer); + } + + // TODO: Make this a promise array and only continue on completion + layerPromises.push(this.addLayer(layer)); }); - var layer = this._featureToLayer(feature, options); - - if (!layer) { - return; - } - - // Sometimes you don't want to store a reference to the feature - // - // For example, to save memory when being used by tile layers - if (this._options.keepFeatures) { - layer.feature = feature; - } - - // If defined, call a function for each feature - // - // This is commonly used for adding event listeners from the user script - if (this._options.onEachFeature) { - this._options.onEachFeature(feature, layer); - } - - // TODO: Make this a promise array and only continue on completion - this.addLayer(layer); - }); - - // If merging layers do that now, otherwise skip as the geometry layers - // should have already outputted themselves - if (!this.isOutput()) { - return; - } - - // From here on we can assume that we want to merge the layers - - var polygonAttributes = []; - var polygonFlat = true; - - var polylineAttributes = []; - var pointAttributes = []; - - this._layers.forEach(layer => { - if (layer instanceof PolygonLayer) { - polygonAttributes.push(layer.getBufferAttributes()); - - if (polygonFlat && !layer.isFlat()) { - polygonFlat = false; + Promise.all(layerPromises).then((results) => { + // If merging layers do that now, otherwise skip as the geometry layers + // should have already outputted themselves + if (!this.isOutput()) { + resolve(); + return; } - } else if (layer instanceof PolylineLayer) { - polylineAttributes.push(layer.getBufferAttributes()); - } else if (layer instanceof PointLayer) { - pointAttributes.push(layer.getBufferAttributes()); - } + + // From here on we can assume that we want to merge the layers + + var polygonAttributes = []; + var polygonAttributeLengths = { + positions: 3, + normals: 3, + colors: 3 + }; + var polygonFlat = true; + + var polylineAttributes = []; + var pointAttributes = []; + + this._layers.forEach(layer => { + if (layer instanceof PolygonLayer) { + polygonAttributes.push(layer.getBufferAttributes()); + + if (polygonFlat && !layer.isFlat()) { + polygonFlat = false; + } + + if (this._options.interactive) { + polygonAttributeLengths.pickingIds = 1; + } + } else if (layer instanceof PolylineLayer) { + polylineAttributes.push(layer.getBufferAttributes()); + } else if (layer instanceof PointLayer) { + pointAttributes.push(layer.getBufferAttributes()); + } + }); + + if (polygonAttributes.length > 0) { + var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes); + this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, polygonFlat).then((result) => { + this._polygonMesh = result.mesh; + this.add(this._polygonMesh); + + if (result.pickingMesh) { + this._pickingMesh.add(pickingMesh); + } + }); + } + + if (polylineAttributes.length > 0) { + var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes); + this._setPolylineMesh(mergedPolylineAttributes); + this.add(this._polylineMesh); + } + + if (pointAttributes.length > 0) { + var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes); + this._setPointMesh(mergedPointAttributes); + this.add(this._pointMesh); + } + + // Clean up layers + // + // TODO: Are there ever situations where the unmerged buffer attributes + // and coordinates would still be required? + this._layers.forEach(layer => { + layer.clearBufferAttributes(); + layer.clearCoordinates(); + }); + + resolve(); + }); }); - - if (polygonAttributes.length > 0) { - var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes); - this._setPolygonMesh(mergedPolygonAttributes, polygonFlat); - this.add(this._polygonMesh); - } - - if (polylineAttributes.length > 0) { - var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes); - this._setPolylineMesh(mergedPolylineAttributes); - this.add(this._polylineMesh); - } - - if (pointAttributes.length > 0) { - var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes); - this._setPointMesh(mergedPointAttributes); - this.add(this._pointMesh); - } - - // Clean up layers - // - // TODO: Are there ever situations where the unmerged buffer attributes - // and coordinates would still be required? - this._layers.forEach(layer => { - layer.clearBufferAttributes(); - layer.clearCoordinates(); - }); - - return Promise.resolve(); } // Create and store mesh from buffer attributes // - // TODO: De-dupe this from the individual mesh creation logic within each - // geometry layer (materials, settings, etc) - // - // Could make this an abstract method for each geometry layer - _setPolygonMesh(attributes, flat) { - var geometry = new THREE.BufferGeometry(); - - // itemSize = 3 because there are 3 values (components) per vertex - geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3)); - geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); - geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); - - if (attributes.pickingIds) { - geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); - } - - geometry.computeBoundingBox(); - + // TODO: Probably remove this and call static method directly as it's just a proxy + _setPolygonMesh(attributes, attributeLengths, flat) { // TODO: Make this work when style is a function per feature var style = (typeof this._options.style === 'function') ? this._options.style(this._geojson.features[0]) : this._options.style; style = extend({}, GeoJSON.defaultStyle, style); - var material; - if (this._options.polygonMaterial && this._options.polygonMaterial instanceof THREE.Material) { - material = this._options.polygonMaterial; - } else if (!this._world._environment._skybox) { - material = new THREE.MeshPhongMaterial({ - vertexColors: THREE.VertexColors, - side: THREE.BackSide, - transparent: style.transparent, - opacity: style.opacity, - blending: style.blending - }); - } else { - material = new THREE.MeshStandardMaterial({ - vertexColors: THREE.VertexColors, - side: THREE.BackSide, - transparent: style.transparent, - opacity: style.opacity, - blending: style.blending - }); - material.roughness = 1; - material.metalness = 0.1; - material.envMapIntensity = 3; - material.envMap = this._world._environment._skybox.getRenderTarget(); - } - - var mesh; - - // Pass mesh through callback, if defined - if (typeof this._options.onPolygonMesh === 'function') { - mesh = this._options.onPolygonMesh(geometry, material); - } else { - mesh = new THREE.Mesh(geometry, material); - - mesh.castShadow = true; - mesh.receiveShadow = true; - } - - if (flat) { - material.depthWrite = false; - mesh.renderOrder = 1; - } - - if (this._options.interactive && this._pickingMesh) { - material = new PickingMaterial(); - material.side = THREE.BackSide; - - var pickingMesh = new THREE.Mesh(geometry, material); - this._pickingMesh.add(pickingMesh); - } - - this._polygonMesh = mesh; + return PolygonLayer.SetMesh(attributes, attributeLengths, flat, style, this._options, this._world._environment._skybox); } _setPolylineMesh(attributes) { diff --git a/src/layer/GeoJSONWorkerLayer.js b/src/layer/GeoJSONWorkerLayer.js index 85abd4a..d834340 100644 --- a/src/layer/GeoJSONWorkerLayer.js +++ b/src/layer/GeoJSONWorkerLayer.js @@ -97,20 +97,15 @@ class GeoJSONWorkerLayer extends Layer { Worker.exec('GeoJSONWorkerLayer.Process', [geojson, topojson, headers, originPoint, style, interactive], transferrables).then((results) => { console.timeEnd('Worker round trip'); - var splitVertices = Buffer.splitFloat32Array(results.attributes.vertices); + var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); var splitNormals = Buffer.splitFloat32Array(results.attributes.normals); - var splitColours = Buffer.splitFloat32Array(results.attributes.colours); + var splitColors = Buffer.splitFloat32Array(results.attributes.colors); var splitProperties; if (results.properties) { splitProperties = Buffer.splitUint8Array(results.properties); } - // var splitPickingIds; - // if (results.pickingIds) { - // splitPickingIds = Buffer.splitFloat32Array(results.attributes.pickingIds); - // } - var flats = results.flats; var objects = []; @@ -125,7 +120,7 @@ class GeoJSONWorkerLayer extends Layer { colors: 3 }; - for (var i = 0; i < splitVertices.length; i++) { + for (var i = 0; i < splitPositions.length; i++) { if (splitProperties && splitProperties[i]) { properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); } else { @@ -136,24 +131,20 @@ class GeoJSONWorkerLayer extends Layer { // the feature, though the current logic isn't aware of that obj = { attributes: [{ - positions: splitVertices[i], + positions: splitPositions[i], normals: splitNormals[i], - colors: splitColours[i] + colors: splitColors[i] }], properties: properties, flat: flats[i] }; - // if (splitPickingIds && splitPickingIds[i]) { - // obj.attributes.pickingIds = splitPickingIds[i]; - // } - // WORKERS: If interactive, generate unique ID for each feature, create // the buffer attributes and set up event listeners if (this._options.interactive) { pickingId = this.getPickingId(); - pickingIds = new Float32Array(splitVertices[i].length / 3); + pickingIds = new Float32Array(splitPositions[i].length / 3); pickingIds.fill(pickingId); obj.attributes[0].pickingIds = pickingIds; @@ -192,28 +183,6 @@ class GeoJSONWorkerLayer extends Layer { polygonAttributes.push(bufferAttributes); }; - // console.log(splitVertices, splitNormals, splitColours, splitPickingIds); - - // var layer; - - // var polygonAttributes = []; - // var polygonFlat = true; - - // objects.forEach((obj, index) => { - // layer = polygonWorkerLayers[index]; - // layer.createGeometry(obj); - - // if (layer.isOutput()) { - // return; - // } - - // polygonAttributes.push(layer.getBufferAttributes()); - - // if (polygonFlat && !layer.isFlat()) { - // polygonFlat = false; - // } - // }); - if (polygonAttributes.length > 0) { var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes); @@ -221,8 +190,14 @@ class GeoJSONWorkerLayer extends Layer { var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; style = extend({}, GeoJSON.defaultStyle, style); - this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat); - this.add(this._polygonMesh); + this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat).then((result) => { + this._polygonMesh = result.mesh; + this.add(this._polygonMesh); + + if (result.pickingMesh) { + this._pickingMesh.add(pickingMesh); + } + }); } resolve(); @@ -337,9 +312,9 @@ class GeoJSONWorkerLayer extends Layer { var transferrables = []; var transferrablesSize = 0; - var vertices = []; + var positions = []; var normals = []; - var colours = []; + var colors = []; // var pickingIds = []; var properties = []; @@ -364,14 +339,9 @@ class GeoJSONWorkerLayer extends Layer { for (var j = 0; j < result.attributes.length; j++) { attributes = result.attributes[j]; - vertices.push(attributes.vertices); + positions.push(attributes.positions); normals.push(attributes.normals); - colours.push(attributes.colours); - - // WORKERS: Handle interaction back in the main thread - // if (attributes.pickingIds) { - // pickingIds.push(attributes.pickingIds); - // } + colors.push(attributes.colors); if (_properties) { properties.push(Buffer.stringToUint8Array(JSON.stringify(polygon.properties))); @@ -380,19 +350,19 @@ class GeoJSONWorkerLayer extends Layer { }; var mergedAttributes = { - vertices: Buffer.mergeFloat32Arrays(vertices), + positions: Buffer.mergeFloat32Arrays(positions), normals: Buffer.mergeFloat32Arrays(normals), - colours: Buffer.mergeFloat32Arrays(colours) + colors: Buffer.mergeFloat32Arrays(colors) }; - transferrables.push(mergedAttributes.vertices[0].buffer); - transferrables.push(mergedAttributes.vertices[1].buffer); + transferrables.push(mergedAttributes.positions[0].buffer); + transferrables.push(mergedAttributes.positions[1].buffer); transferrables.push(mergedAttributes.normals[0].buffer); transferrables.push(mergedAttributes.normals[1].buffer); - transferrables.push(mergedAttributes.colours[0].buffer); - transferrables.push(mergedAttributes.colours[1].buffer); + transferrables.push(mergedAttributes.colors[0].buffer); + transferrables.push(mergedAttributes.colors[1].buffer); var mergedProperties; if (_properties) { @@ -402,13 +372,6 @@ class GeoJSONWorkerLayer extends Layer { transferrables.push(mergedProperties[1].buffer); } - // WORKERS: Handle interaction back in the main thread - // if (pickingIds.length > 0) { - // mergedAttributes.pickingIds = Buffer.mergeFloat32Arrays(pickingIds); - // transferrables.push(mergedAttributes.pickingIds[0].buffer); - // transferrables.push(mergedAttributes.pickingIds[1].buffer); - // } - var output = { attributes: mergedAttributes, flats: flats @@ -452,70 +415,7 @@ class GeoJSONWorkerLayer extends Layer { // // Could make this an abstract method for each geometry layer _setPolygonMesh(attributes, attributeLengths, style, flat) { - var geometry = new THREE.BufferGeometry(); - - for (var key in attributes) { - geometry.addAttribute(key.slice(0, -1), new THREE.BufferAttribute(attributes[key], attributeLengths[key])); - } - - geometry.computeBoundingBox(); - - // Temporary until the above style logic is fixed for workers - // var style = extend({}, GeoJSON.defaultStyle); - - var material; - if (this._options.polygonMaterial && this._options.polygonMaterial instanceof THREE.Material) { - material = this._options.polygonMaterial; - } else if (!this._world._environment._skybox) { - material = new THREE.MeshPhongMaterial({ - vertexColors: THREE.VertexColors, - side: THREE.BackSide, - transparent: style.transparent, - opacity: style.opacity, - blending: style.blending - }); - } else { - material = new THREE.MeshStandardMaterial({ - vertexColors: THREE.VertexColors, - side: THREE.BackSide, - transparent: style.transparent, - opacity: style.opacity, - blending: style.blending - }); - material.roughness = 1; - material.metalness = 0.1; - material.envMapIntensity = 3; - material.envMap = this._world._environment._skybox.getRenderTarget(); - } - - var mesh; - - // Pass mesh through callback, if defined - if (typeof this._options.onPolygonMesh === 'function') { - mesh = this._options.onPolygonMesh(geometry, material); - } else { - mesh = new THREE.Mesh(geometry, material); - - mesh.castShadow = true; - mesh.receiveShadow = true; - } - - if (flat) { - material.depthWrite = false; - mesh.renderOrder = 1; - } - - if (this._options.interactive && this._pickingMesh) { - material = new PickingMaterial(); - material.side = THREE.BackSide; - - var pickingMesh = new THREE.Mesh(geometry, material); - this._pickingMesh.add(pickingMesh); - - this.addToPicking(this._pickingMesh); - } - - this._polygonMesh = mesh; + return PolygonLayer.SetMesh(attributes, attributeLengths, flat, style, this._options, this._world._environment._skybox); } // Set up and re-emit interaction events diff --git a/src/layer/geometry/PolygonLayer.js b/src/layer/geometry/PolygonLayer.js index de732d7..ee69f41 100644 --- a/src/layer/geometry/PolygonLayer.js +++ b/src/layer/geometry/PolygonLayer.js @@ -57,34 +57,57 @@ class PolygonLayer extends Layer { } _onAdd(world) { - this._setCoordinates(); + return new Promise((resolve, reject) => { + this._setCoordinates(); - if (this._options.interactive) { - // Only add to picking mesh if this layer is controlling output - // - // Otherwise, assume another component will eventually add a mesh to - // the picking scene - if (this.isOutput()) { - this._pickingMesh = new THREE.Object3D(); - this.addToPicking(this._pickingMesh); + if (this._options.interactive) { + // Only add to picking mesh if this layer is controlling output + // + // Otherwise, assume another component will eventually add a mesh to + // the picking scene + if (this.isOutput()) { + this._pickingMesh = new THREE.Object3D(); + this.addToPicking(this._pickingMesh); + } + + this._setPickingId(); + this._addPickingEvents(); } - this._setPickingId(); - this._addPickingEvents(); - } + PolygonLayer.SetBufferAttributes(this._projectedCoordinates, this._options).then((result) => { + this._bufferAttributes = Buffer.mergeAttributes(result.attributes); + this._flat = result.flat; - // Store geometry representation as instances of THREE.BufferAttribute - this._setBufferAttributes(); + var attributeLengths = { + positions: 3, + normals: 3, + colors: 3 + }; - if (this.isOutput()) { - // Set mesh if not merging elsewhere - this._setMesh(this._bufferAttributes); + if (this._options.interactive) { + attributeLengths.pickingIds = 1; + } - // Output mesh - this.add(this._mesh); - } + if (this.isOutput()) { + var style = this._options.style; - return Promise.resolve(this); + // Set mesh if not merging elsewhere + PolygonLayer.SetMesh(this._bufferAttributes, attributeLengths, this._flat, style, this._options, this._world._environment._skybox).then((result) => { + // Output mesh + this.add(result.mesh); + + if (result.pickingMesh) { + this._pickingMesh.add(pickingMesh); + } + }); + } + + result.attributes = null; + result = null; + + resolve(this); + }).catch(reject); + }); } // Return center of polygon as a LatLon @@ -117,122 +140,6 @@ class PolygonLayer extends Layer { } // Create and store reference to THREE.BufferAttribute data for this layer - // - // TODO: Remove this and instead use the SetBufferAttributes static method - _setBufferAttributes() { - var attributes; - - // Only use this if you know what you're doing - if (typeof this._options.onBufferAttributes === 'function') { - // TODO: Probably want to pass something less general as arguments, - // though passing the instance will do for now (it's everything) - attributes = this._options.onBufferAttributes(this); - } else { - var height = 0; - - // Convert height into world units - if (this._options.style.height && this._options.style.height !== 0) { - height = this._world.metresToWorld(this._options.style.height, this._pointScale); - } - - var colour = new THREE.Color(); - colour.set(this._options.style.color); - - // Light and dark colours used for poor-mans AO gradient on object sides - var light = new THREE.Color(0xffffff); - var shadow = new THREE.Color(0x666666); - - // For each polygon - attributes = this._projectedCoordinates.map(_projectedCoordinates => { - // Convert coordinates to earcut format - var _earcut = PolygonLayer.ToEarcut(_projectedCoordinates); - - // Triangulate faces using earcut - var faces = PolygonLayer.Triangulate(_earcut.vertices, _earcut.holes, _earcut.dimensions); - - var groupedVertices = []; - for (i = 0, il = _earcut.vertices.length; i < il; i += _earcut.dimensions) { - groupedVertices.push(_earcut.vertices.slice(i, i + _earcut.dimensions)); - } - - var extruded = extrudePolygon(groupedVertices, faces, { - bottom: 0, - top: height - }); - - var topColor = colour.clone().multiply(light); - var bottomColor = colour.clone().multiply(shadow); - - var _vertices = extruded.positions; - var _faces = []; - var _colours = []; - - var _colour; - extruded.top.forEach((face, fi) => { - _colour = []; - - _colour.push([colour.r, colour.g, colour.b]); - _colour.push([colour.r, colour.g, colour.b]); - _colour.push([colour.r, colour.g, colour.b]); - - _faces.push(face); - _colours.push(_colour); - }); - - this._flat = true; - - if (extruded.sides) { - this._flat = false; - - // Set up colours for every vertex with poor-mans AO on the sides - extruded.sides.forEach((face, fi) => { - _colour = []; - - // First face is always bottom-bottom-top - if (fi % 2 === 0) { - _colour.push([bottomColor.r, bottomColor.g, bottomColor.b]); - _colour.push([bottomColor.r, bottomColor.g, bottomColor.b]); - _colour.push([topColor.r, topColor.g, topColor.b]); - // Reverse winding for the second face - // top-top-bottom - } else { - _colour.push([topColor.r, topColor.g, topColor.b]); - _colour.push([topColor.r, topColor.g, topColor.b]); - _colour.push([bottomColor.r, bottomColor.g, bottomColor.b]); - } - - _faces.push(face); - _colours.push(_colour); - }); - } - - // Skip bottom as there's no point rendering it - // allFaces.push(extruded.faces); - - var polygon = { - vertices: _vertices, - faces: _faces, - colours: _colours, - facesCount: _faces.length - }; - - if (this._options.interactive && this._pickingId) { - // Inject picking ID - polygon.pickingId = this._pickingId; - } - - // Convert polygon representation to proper attribute arrays - return PolygonLayer.ToAttributes(polygon); - }); - } - - this._bufferAttributes = Buffer.mergeAttributes(attributes); - - // Original attributes are no longer required so free the memory - attributes = null; - } - - // TODO: Ensure that this has feature parity with the non-static method static SetBufferAttributes(coordinates, options) { return new Promise((resolve) => { var height = 0; @@ -365,50 +272,125 @@ class PolygonLayer extends Layer { // Create and store mesh from buffer attributes // // This is only called if the layer is controlling its own output - _setMesh(attributes) { + // + // TODO: Dedupe with code used by workers, perhaps via a static method + // _setMesh(attributes) { + // var geometry = new THREE.BufferGeometry(); + + // // itemSize = 3 because there are 3 values (components) per vertex + // geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3)); + // geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); + // geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); + + // if (attributes.pickingIds) { + // geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); + // } + + // geometry.computeBoundingBox(); + + // var material; + // if (this._options.material && this._options.material instanceof THREE.Material) { + // material = this._options.material; + // } else if (!this._world._environment._skybox) { + // material = new THREE.MeshPhongMaterial({ + // vertexColors: THREE.VertexColors, + // side: THREE.BackSide, + // transparent: this._options.style.transparent, + // opacity: this._options.style.opacity, + // blending: this._options.style.blending + // }); + // } else { + // material = new THREE.MeshStandardMaterial({ + // vertexColors: THREE.VertexColors, + // side: THREE.BackSide, + // transparent: this._options.style.transparent, + // opacity: this._options.style.opacity, + // blending: this._options.style.blending + // }); + // material.roughness = 1; + // material.metalness = 0.1; + // material.envMapIntensity = 3; + // material.envMap = this._world._environment._skybox.getRenderTarget(); + // } + + // var mesh; + + // // Pass mesh through callback, if defined + // if (typeof this._options.onMesh === 'function') { + // mesh = this._options.onMesh(geometry, material); + // } else { + // mesh = new THREE.Mesh(geometry, material); + + // mesh.castShadow = true; + // mesh.receiveShadow = true; + // } + + // if (this.isFlat()) { + // material.depthWrite = false; + // mesh.renderOrder = 1; + // } + + // if (this._options.interactive && this._pickingMesh) { + // material = new PickingMaterial(); + // material.side = THREE.BackSide; + + // var pickingMesh = new THREE.Mesh(geometry, material); + // this._pickingMesh.add(pickingMesh); + // } + + // this._mesh = mesh; + // } + + static SetMesh(attributes, attributeLengths, flat, style, options, skybox) { var geometry = new THREE.BufferGeometry(); - // itemSize = 3 because there are 3 values (components) per vertex - geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3)); - geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); - geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); + // TODO: Remove + // // itemSize = 3 because there are 3 values (components) per vertex + // geometry.addAttribute('position', new THREE.BufferAttribute(attributes.positions, 3)); + // geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); + // geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); - if (attributes.pickingIds) { - geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); + // TODO: Remove + // if (attributes.pickingIds) { + // geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); + // } + + for (var key in attributes) { + geometry.addAttribute(key.slice(0, -1), new THREE.BufferAttribute(attributes[key], attributeLengths[key])); } geometry.computeBoundingBox(); var material; - if (this._options.material && this._options.material instanceof THREE.Material) { - material = this._options.material; - } else if (!this._world._environment._skybox) { + if (options.polygonMaterial && options.polygonMaterial instanceof THREE.Material) { + material = options.polygonMaterial; + } else if (!skybox) { material = new THREE.MeshPhongMaterial({ vertexColors: THREE.VertexColors, side: THREE.BackSide, - transparent: this._options.style.transparent, - opacity: this._options.style.opacity, - blending: this._options.style.blending + transparent: style.transparent, + opacity: style.opacity, + blending: style.blending }); } else { material = new THREE.MeshStandardMaterial({ vertexColors: THREE.VertexColors, side: THREE.BackSide, - transparent: this._options.style.transparent, - opacity: this._options.style.opacity, - blending: this._options.style.blending + transparent: style.transparent, + opacity: style.opacity, + blending: style.blending }); material.roughness = 1; material.metalness = 0.1; material.envMapIntensity = 3; - material.envMap = this._world._environment._skybox.getRenderTarget(); + material.envMap = skybox.getRenderTarget(); } var mesh; // Pass mesh through callback, if defined - if (typeof this._options.onMesh === 'function') { - mesh = this._options.onMesh(geometry, material); + if (typeof options.onPolygonMesh === 'function') { + mesh = options.onPolygonMesh(geometry, material); } else { mesh = new THREE.Mesh(geometry, material); @@ -416,20 +398,25 @@ class PolygonLayer extends Layer { mesh.receiveShadow = true; } - if (this.isFlat()) { + if (flat) { material.depthWrite = false; mesh.renderOrder = 1; } - if (this._options.interactive && this._pickingMesh) { + if (options.interactive) { material = new PickingMaterial(); material.side = THREE.BackSide; var pickingMesh = new THREE.Mesh(geometry, material); - this._pickingMesh.add(pickingMesh); + + // TODO: Move this to after whatever calls PolygonLayer.SetMesh() + // this._pickingMesh.add(pickingMesh); } - this._mesh = mesh; + return Promise.resolve({ + mesh: mesh, + pickingMesh: pickingMesh + }); } // Convert and project coordinates @@ -531,7 +518,7 @@ class PolygonLayer extends Layer { // TODO: Can this be simplified? It's messy and huge static ToAttributes(polygon) { // Three components per vertex per face (3 x 3 = 9) - var vertices = new Float32Array(polygon.facesCount * 9); + var positions = new Float32Array(polygon.facesCount * 9); var normals = new Float32Array(polygon.facesCount * 9); var colours = new Float32Array(polygon.facesCount * 9); @@ -603,9 +590,9 @@ class PolygonLayer extends Layer { var ny = cb.y; var nz = cb.z; - vertices[lastIndex * 9 + 0] = ax; - vertices[lastIndex * 9 + 1] = ay; - vertices[lastIndex * 9 + 2] = az; + positions[lastIndex * 9 + 0] = ax; + positions[lastIndex * 9 + 1] = ay; + positions[lastIndex * 9 + 2] = az; normals[lastIndex * 9 + 0] = nx; normals[lastIndex * 9 + 1] = ny; @@ -615,9 +602,9 @@ class PolygonLayer extends Layer { colours[lastIndex * 9 + 1] = c1[1]; colours[lastIndex * 9 + 2] = c1[2]; - vertices[lastIndex * 9 + 3] = bx; - vertices[lastIndex * 9 + 4] = by; - vertices[lastIndex * 9 + 5] = bz; + positions[lastIndex * 9 + 3] = bx; + positions[lastIndex * 9 + 4] = by; + positions[lastIndex * 9 + 5] = bz; normals[lastIndex * 9 + 3] = nx; normals[lastIndex * 9 + 4] = ny; @@ -627,9 +614,9 @@ class PolygonLayer extends Layer { colours[lastIndex * 9 + 4] = c2[1]; colours[lastIndex * 9 + 5] = c2[2]; - vertices[lastIndex * 9 + 6] = cx; - vertices[lastIndex * 9 + 7] = cy; - vertices[lastIndex * 9 + 8] = cz; + positions[lastIndex * 9 + 6] = cx; + positions[lastIndex * 9 + 7] = cy; + positions[lastIndex * 9 + 8] = cz; normals[lastIndex * 9 + 6] = nx; normals[lastIndex * 9 + 7] = ny; @@ -649,9 +636,9 @@ class PolygonLayer extends Layer { } var attributes = { - vertices: vertices, + positions: positions, normals: normals, - colours: colours + colors: colours }; if (pickingIds) { From 9ee3823e2dd2f6ab7769c541d569b424943f41b3 Mon Sep 17 00:00:00 2001 From: Robin Hawkes Date: Thu, 8 Sep 2016 14:14:47 +0100 Subject: [PATCH 3/4] Removed unneeded code --- src/layer/geometry/PolygonLayer.js | 72 ------------------------------ 1 file changed, 72 deletions(-) diff --git a/src/layer/geometry/PolygonLayer.js b/src/layer/geometry/PolygonLayer.js index ee69f41..95b5cd7 100644 --- a/src/layer/geometry/PolygonLayer.js +++ b/src/layer/geometry/PolygonLayer.js @@ -269,78 +269,6 @@ class PolygonLayer extends Layer { this._projectedCoordinates = null; } - // Create and store mesh from buffer attributes - // - // This is only called if the layer is controlling its own output - // - // TODO: Dedupe with code used by workers, perhaps via a static method - // _setMesh(attributes) { - // var geometry = new THREE.BufferGeometry(); - - // // itemSize = 3 because there are 3 values (components) per vertex - // geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3)); - // geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); - // geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); - - // if (attributes.pickingIds) { - // geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); - // } - - // geometry.computeBoundingBox(); - - // var material; - // if (this._options.material && this._options.material instanceof THREE.Material) { - // material = this._options.material; - // } else if (!this._world._environment._skybox) { - // material = new THREE.MeshPhongMaterial({ - // vertexColors: THREE.VertexColors, - // side: THREE.BackSide, - // transparent: this._options.style.transparent, - // opacity: this._options.style.opacity, - // blending: this._options.style.blending - // }); - // } else { - // material = new THREE.MeshStandardMaterial({ - // vertexColors: THREE.VertexColors, - // side: THREE.BackSide, - // transparent: this._options.style.transparent, - // opacity: this._options.style.opacity, - // blending: this._options.style.blending - // }); - // material.roughness = 1; - // material.metalness = 0.1; - // material.envMapIntensity = 3; - // material.envMap = this._world._environment._skybox.getRenderTarget(); - // } - - // var mesh; - - // // Pass mesh through callback, if defined - // if (typeof this._options.onMesh === 'function') { - // mesh = this._options.onMesh(geometry, material); - // } else { - // mesh = new THREE.Mesh(geometry, material); - - // mesh.castShadow = true; - // mesh.receiveShadow = true; - // } - - // if (this.isFlat()) { - // material.depthWrite = false; - // mesh.renderOrder = 1; - // } - - // if (this._options.interactive && this._pickingMesh) { - // material = new PickingMaterial(); - // material.side = THREE.BackSide; - - // var pickingMesh = new THREE.Mesh(geometry, material); - // this._pickingMesh.add(pickingMesh); - // } - - // this._mesh = mesh; - // } - static SetMesh(attributes, attributeLengths, flat, style, options, skybox) { var geometry = new THREE.BufferGeometry(); From ff8035ad5c73f96ea7cee561b1b06e4431065ff2 Mon Sep 17 00:00:00 2001 From: Robin Hawkes Date: Thu, 8 Sep 2016 14:17:15 +0100 Subject: [PATCH 4/4] Removed unneeded code --- src/layer/geometry/PolygonLayer.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/layer/geometry/PolygonLayer.js b/src/layer/geometry/PolygonLayer.js index 95b5cd7..14572bc 100644 --- a/src/layer/geometry/PolygonLayer.js +++ b/src/layer/geometry/PolygonLayer.js @@ -272,17 +272,6 @@ class PolygonLayer extends Layer { static SetMesh(attributes, attributeLengths, flat, style, options, skybox) { var geometry = new THREE.BufferGeometry(); - // TODO: Remove - // // itemSize = 3 because there are 3 values (components) per vertex - // geometry.addAttribute('position', new THREE.BufferAttribute(attributes.positions, 3)); - // geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3)); - // geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3)); - - // TODO: Remove - // if (attributes.pickingIds) { - // geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1)); - // } - for (var key in attributes) { geometry.addAttribute(key.slice(0, -1), new THREE.BufferAttribute(attributes[key], attributeLengths[key])); } @@ -336,9 +325,6 @@ class PolygonLayer extends Layer { material.side = THREE.BackSide; var pickingMesh = new THREE.Mesh(geometry, material); - - // TODO: Move this to after whatever calls PolygonLayer.SetMesh() - // this._pickingMesh.add(pickingMesh); } return Promise.resolve({