2016-02-18 22:03:56 +00:00
|
|
|
|
import TileLayer from './TileLayer';
|
|
|
|
|
import ImageTile from './ImageTile';
|
|
|
|
|
import ImageTileLayerBaseMaterial from './ImageTileLayerBaseMaterial';
|
|
|
|
|
import throttle from 'lodash.throttle';
|
2019-02-16 14:49:50 +00:00
|
|
|
|
import * as THREE from 'three';
|
2016-02-23 23:11:49 +00:00
|
|
|
|
import extend from 'lodash.assign';
|
2019-02-16 14:49:50 +00:00
|
|
|
|
import Geo from '../../geo/Geo';
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
2016-02-28 18:40:51 +00:00
|
|
|
|
// TODO: Make sure nothing is left behind in the heap after calling destroy()
|
|
|
|
|
|
2016-02-18 22:03:56 +00:00
|
|
|
|
// DONE: Find a way to avoid the flashing caused by the gap between old tiles
|
|
|
|
|
// being removed and the new tiles being ready for display
|
|
|
|
|
//
|
|
|
|
|
// DONE: Simplest first step for MVP would be to give each tile mesh the colour
|
|
|
|
|
// of the basemap ground so it blends in a little more, or have a huge ground
|
|
|
|
|
// plane underneath all the tiles that shows through between tile updates.
|
|
|
|
|
//
|
|
|
|
|
// Could keep the old tiles around until the new ones are ready, though they'd
|
|
|
|
|
// probably need to be layered in a way so the old tiles don't overlap new ones,
|
|
|
|
|
// which is similar to how Leaflet approaches this (it has 2 layers)
|
|
|
|
|
//
|
|
|
|
|
// Could keep the tile from the previous quadtree level visible until all 4
|
|
|
|
|
// tiles at the new / current level have finished loading and are displayed.
|
|
|
|
|
// Perhaps by keeping a map of tiles by quadcode and a boolean for each of the
|
|
|
|
|
// child quadcodes showing whether they are loaded and in view. If all true then
|
|
|
|
|
// remove the parent tile, otherwise keep it on a lower layer.
|
|
|
|
|
|
|
|
|
|
// TODO: Load and display a base layer separate to the LOD grid that is at a low
|
|
|
|
|
// resolution – used as a backup / background to fill in empty areas / distance
|
|
|
|
|
|
|
|
|
|
// DONE: Fix the issue where some tiles just don't load, or at least the texture
|
|
|
|
|
// never shows up – tends to happen if you quickly zoom in / out past it while
|
|
|
|
|
// it's still loading, leaving a blank space
|
|
|
|
|
|
|
|
|
|
// TODO: Optimise the request of many image tiles – look at how Leaflet and
|
|
|
|
|
// OpenWebGlobe approach this (eg. batching, queues, etc)
|
|
|
|
|
|
|
|
|
|
// TODO: Cancel pending tile requests if they get removed from view before they
|
|
|
|
|
// reach a ready state (eg. cancel image requests, etc). Need to ensure that the
|
|
|
|
|
// images are re-requested when the tile is next in scene (even if from cache)
|
|
|
|
|
|
2016-02-19 13:55:35 +00:00
|
|
|
|
// TODO: Consider not performing an LOD calculation on every frame, instead only
|
|
|
|
|
// on move end so panning, orbiting and zooming stays smooth. Otherwise it's
|
|
|
|
|
// possible for performance to tank if you pan, orbit or zoom rapidly while all
|
|
|
|
|
// the LOD calculations are being made and new tiles requested.
|
|
|
|
|
//
|
|
|
|
|
// Pending tiles should continue to be requested and output to the scene on each
|
|
|
|
|
// frame, but no new LOD calculations should be made.
|
|
|
|
|
|
2016-02-27 11:04:40 +00:00
|
|
|
|
// This tile layer both updates the quadtree and outputs tiles on every frame
|
|
|
|
|
// (throttled to some amount)
|
|
|
|
|
//
|
|
|
|
|
// This is because the computational complexity of image tiles is generally low
|
|
|
|
|
// and so there isn't much jank when running these calculations and outputs in
|
|
|
|
|
// realtime
|
|
|
|
|
//
|
|
|
|
|
// The benefit to doing this is that the underlying map layer continues to
|
|
|
|
|
// refresh and update during movement, which is an arguably better experience
|
|
|
|
|
|
2016-02-18 22:03:56 +00:00
|
|
|
|
class ImageTileLayer extends TileLayer {
|
|
|
|
|
constructor(path, options) {
|
2016-02-23 23:11:49 +00:00
|
|
|
|
var defaults = {
|
2019-02-16 14:49:50 +00:00
|
|
|
|
distance: 300000 * Geo.multiplier
|
2016-02-23 23:11:49 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-02-28 20:29:52 +00:00
|
|
|
|
options = extend({}, defaults, options);
|
2016-02-23 23:11:49 +00:00
|
|
|
|
|
2016-02-19 21:25:16 +00:00
|
|
|
|
super(options);
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
|
|
|
|
this._path = path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onAdd(world) {
|
2016-08-30 15:46:04 +00:00
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
super._onAdd(world).then(() => {
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// TODO: Removed because it causes depth buffer intersection issues
|
|
|
|
|
// with layer on top for some reason. Need to work out why and fix.
|
|
|
|
|
//
|
2016-08-30 15:46:04 +00:00
|
|
|
|
// Add base layer
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// var geom = new THREE.PlaneBufferGeometry(2000000, 2000000, 1);
|
|
|
|
|
|
|
|
|
|
// var baseMaterial;
|
|
|
|
|
// if (this._world._environment._skybox) {
|
|
|
|
|
// baseMaterial = ImageTileLayerBaseMaterial('#f5f5f3', this._world._environment._skybox.getRenderTarget());
|
|
|
|
|
// } else {
|
|
|
|
|
// baseMaterial = ImageTileLayerBaseMaterial('#f5f5f3');
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// var mesh = new THREE.Mesh(geom, baseMaterial);
|
2016-08-30 15:46:04 +00:00
|
|
|
|
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// // Setting this causes a depth-buffer intersection issue on the
|
|
|
|
|
// // all-the-things example
|
|
|
|
|
// // mesh.renderOrder = -1;
|
2016-08-30 15:46:04 +00:00
|
|
|
|
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// mesh.rotation.x = -90 * Math.PI / 180;
|
2016-08-30 15:46:04 +00:00
|
|
|
|
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// // TODO: It might be overkill to receive a shadow on the base layer as it's
|
|
|
|
|
// // rarely seen (good to have if performance difference is negligible)
|
|
|
|
|
// mesh.receiveShadow = true;
|
2016-08-30 15:46:04 +00:00
|
|
|
|
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// this._baseLayer = mesh;
|
|
|
|
|
// this.add(mesh);
|
2016-08-30 15:46:04 +00:00
|
|
|
|
|
|
|
|
|
// 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();
|
2017-01-13 12:25:21 +00:00
|
|
|
|
resolve(this);
|
2016-08-30 15:46:04 +00:00
|
|
|
|
}, 0);
|
|
|
|
|
}).catch(reject);
|
|
|
|
|
});
|
2016-02-18 22:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_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);
|
2016-10-07 10:46:52 +00:00
|
|
|
|
// this._world.on('move', this._onWorldMove, this);
|
2016-02-18 22:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onWorldUpdate() {
|
|
|
|
|
this._calculateLOD();
|
2016-02-27 11:04:40 +00:00
|
|
|
|
this._outputTiles();
|
2016-02-18 22:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onWorldMove(latlon, point) {
|
2017-01-13 12:25:21 +00:00
|
|
|
|
// this._moveBaseLayer(point);
|
2016-02-18 22:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_moveBaseLayer(point) {
|
|
|
|
|
this._baseLayer.position.x = point.x;
|
|
|
|
|
this._baseLayer.position.z = point.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_createTile(quadcode, layer) {
|
2016-03-01 20:51:38 +00:00
|
|
|
|
return new ImageTile(quadcode, this._path, layer);
|
2016-02-18 22:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// Dispose of mesh and materials
|
2017-01-13 12:25:21 +00:00
|
|
|
|
// this._baseLayer.geometry.dispose();
|
|
|
|
|
// this._baseLayer.geometry = null;
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
2017-01-13 12:25:21 +00:00
|
|
|
|
// if (this._baseLayer.material.map) {
|
|
|
|
|
// this._baseLayer.material.map.dispose();
|
|
|
|
|
// this._baseLayer.material.map = null;
|
|
|
|
|
// }
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
2017-01-13 12:25:21 +00:00
|
|
|
|
// this._baseLayer.material.dispose();
|
|
|
|
|
// this._baseLayer.material = null;
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
2017-01-13 12:25:21 +00:00
|
|
|
|
// this._baseLayer = null;
|
2016-02-18 22:03:56 +00:00
|
|
|
|
|
|
|
|
|
// Run common destruction logic from parent
|
|
|
|
|
super.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 20:34:53 +00:00
|
|
|
|
export default ImageTileLayer;
|
|
|
|
|
|
|
|
|
|
var noNew = function(path, options) {
|
2016-02-18 22:03:56 +00:00
|
|
|
|
return new ImageTileLayer(path, options);
|
|
|
|
|
};
|
2016-03-01 20:34:53 +00:00
|
|
|
|
|
|
|
|
|
// Initialise without requiring new keyword
|
|
|
|
|
export {noNew as imageTileLayer};
|