kopia lustrzana https://github.com/robhawkes/vizicities
178 wiersze
6.1 KiB
JavaScript
Executable File
178 wiersze
6.1 KiB
JavaScript
Executable File
import TileLayer from './TileLayer';
|
||
import ImageTile from './ImageTile';
|
||
import ImageTileLayerBaseMaterial from './ImageTileLayerBaseMaterial';
|
||
import throttle from 'lodash.throttle';
|
||
import THREE from 'three';
|
||
import extend from 'lodash.assign';
|
||
|
||
// TODO: Make sure nothing is left behind in the heap after calling destroy()
|
||
|
||
// 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)
|
||
|
||
// 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.
|
||
|
||
// 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
|
||
|
||
class ImageTileLayer extends TileLayer {
|
||
constructor(path, options) {
|
||
var defaults = {
|
||
distance: 300000
|
||
};
|
||
|
||
options = extend({}, defaults, options);
|
||
|
||
super(options);
|
||
|
||
this._path = path;
|
||
}
|
||
|
||
_onAdd(world) {
|
||
return new Promise((resolve, reject) => {
|
||
super._onAdd(world).then(() => {
|
||
// TODO: Removed because it causes depth buffer intersection issues
|
||
// with layer on top for some reason. Need to work out why and fix.
|
||
//
|
||
// Add base layer
|
||
// 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);
|
||
|
||
// // Setting this causes a depth-buffer intersection issue on the
|
||
// // all-the-things example
|
||
// // mesh.renderOrder = -1;
|
||
|
||
// mesh.rotation.x = -90 * Math.PI / 180;
|
||
|
||
// // 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;
|
||
|
||
// this._baseLayer = mesh;
|
||
// this.add(mesh);
|
||
|
||
// 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();
|
||
resolve(this);
|
||
}, 0);
|
||
}).catch(reject);
|
||
});
|
||
}
|
||
|
||
_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);
|
||
}
|
||
|
||
_onWorldUpdate() {
|
||
this._calculateLOD();
|
||
this._outputTiles();
|
||
}
|
||
|
||
_onWorldMove(latlon, point) {
|
||
// this._moveBaseLayer(point);
|
||
}
|
||
|
||
_moveBaseLayer(point) {
|
||
this._baseLayer.position.x = point.x;
|
||
this._baseLayer.position.z = point.y;
|
||
}
|
||
|
||
_createTile(quadcode, layer) {
|
||
return new ImageTile(quadcode, this._path, layer);
|
||
}
|
||
|
||
// 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
|
||
// this._baseLayer.geometry.dispose();
|
||
// this._baseLayer.geometry = null;
|
||
|
||
// if (this._baseLayer.material.map) {
|
||
// this._baseLayer.material.map.dispose();
|
||
// this._baseLayer.material.map = null;
|
||
// }
|
||
|
||
// this._baseLayer.material.dispose();
|
||
// this._baseLayer.material = null;
|
||
|
||
// this._baseLayer = null;
|
||
|
||
// Run common destruction logic from parent
|
||
super.destroy();
|
||
}
|
||
}
|
||
|
||
export default ImageTileLayer;
|
||
|
||
var noNew = function(path, options) {
|
||
return new ImageTileLayer(path, options);
|
||
};
|
||
|
||
// Initialise without requiring new keyword
|
||
export {noNew as imageTileLayer};
|