From 2aa638a451980378bf5ed5cf09a3f9710f15c0fd Mon Sep 17 00:00:00 2001 From: rodri Date: Sat, 21 Sep 2024 07:26:25 -0400 Subject: [PATCH 1/5] support SRGB color space --- source/examples/transition.ts | 5 ++++- source/nodes/MapNode.ts | 5 ++++- source/nodes/MapSphereNode.ts | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/source/examples/transition.ts b/source/examples/transition.ts index 4ccfca4..37a74f2 100644 --- a/source/examples/transition.ts +++ b/source/examples/transition.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import {WebGLRenderer, Scene, Color, TextureLoader, Mesh, SphereGeometry, MeshBasicMaterial, PerspectiveCamera, MOUSE, AmbientLight, Raycaster, Vector2, LinearSRGBColorSpace, ColorManagement} from 'three'; +import {WebGLRenderer, Scene, Color, TextureLoader, Mesh, SphereGeometry, MeshBasicMaterial, PerspectiveCamera, MOUSE, AmbientLight, Raycaster, Vector2, LinearSRGBColorSpace, ColorManagement, REVISION} from 'three'; import {MapControls} from 'three/examples/jsm/controls/MapControls.js'; import {UnitsUtils, BingMapsProvider, MapView} from '../Main'; @@ -32,6 +32,9 @@ function createWorldScene(): any var loader = new TextureLoader(); loader.load('2k_earth_daymap.jpg', function(texture) { + if(parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb' + } var sphere = new Mesh(new SphereGeometry(UnitsUtils.EARTH_RADIUS, 256, 256), new MeshBasicMaterial({map: texture})); scene.add(sphere); }); diff --git a/source/nodes/MapNode.ts b/source/nodes/MapNode.ts index 541527d..9cda566 100644 --- a/source/nodes/MapNode.ts +++ b/source/nodes/MapNode.ts @@ -1,4 +1,4 @@ -import {LinearFilter, Material, Mesh, Texture, Vector3, BufferGeometry, Object3D, RGBAFormat} from 'three'; +import {LinearFilter, Material, Mesh, Texture, Vector3, BufferGeometry, Object3D, RGBAFormat, REVISION} from 'three'; import {MapView} from '../MapView'; import {TextureUtils} from '../utils/TextureUtils'; @@ -286,6 +286,9 @@ export abstract class MapNode extends Mesh } const texture = new Texture(image); + if(parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb' + } texture.generateMipmaps = false; texture.format = RGBAFormat; texture.magFilter = LinearFilter; diff --git a/source/nodes/MapSphereNode.ts b/source/nodes/MapSphereNode.ts index ccaa86f..931f3a0 100644 --- a/source/nodes/MapSphereNode.ts +++ b/source/nodes/MapSphereNode.ts @@ -1,4 +1,4 @@ -import {Matrix4, BufferGeometry, Quaternion, Vector3, Raycaster, Intersection, ShaderMaterial, TextureLoader, Texture, Vector4} from 'three'; +import {Matrix4, BufferGeometry, Quaternion, Vector3, Raycaster, Intersection, ShaderMaterial, TextureLoader, Texture, Vector4, REVISION} from 'three'; import {MapNode, QuadTreePosition} from './MapNode'; import {MapSphereNodeGeometry} from '../geometries/MapSphereNodeGeometry'; import {UnitsUtils} from '../utils/UnitsUtils'; @@ -67,6 +67,12 @@ export class MapSphereNode extends MapNode vec4 color = texture2D(uTexture, vec2(x, y)); gl_FragColor = color; + ${ + parseInt(REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(REVISION) >= 154) ? '' : ''} + ` + } } `; @@ -127,7 +133,11 @@ export class MapSphereNode extends MapNode public async applyTexture(image: HTMLImageElement): Promise { const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function() {}); + const texture = textureLoader.load(image.src, function() { + if(parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb' + } + }); // @ts-ignore this.material.uniforms.uTexture.value = texture; // @ts-ignore From 7998af512ac6401a2b757fb8eb5132c69da6f2c3 Mon Sep 17 00:00:00 2001 From: rodri Date: Sat, 21 Sep 2024 07:29:23 -0400 Subject: [PATCH 2/5] update artifacts --- build/geo-three.cjs | 175 +- build/geo-three.js | 3593 +++++++++++++++---------------- build/geo-three.module.js | 3595 ++++++++++++++++---------------- build/nodes/MapNode.d.ts | 1 + build/nodes/MapSphereNode.d.ts | 1 + build/utils/UnitsUtils.d.ts | 5 + examples/basic.js | 605 +++++- examples/providers.js | 605 +++++- examples/transition.js | 238 ++- 9 files changed, 5144 insertions(+), 3674 deletions(-) diff --git a/build/geo-three.cjs b/build/geo-three.cjs index 6dc3285..ee935f9 100644 --- a/build/geo-three.cjs +++ b/build/geo-three.cjs @@ -2,29 +2,29 @@ var three = require('three'); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -168,16 +168,7 @@ class MapNode extends three.Mesh { } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; + yield this.applyTexture(image); } catch (e) { if (this.disposed) { @@ -189,6 +180,23 @@ class MapNode extends three.Mesh { this.material.needsUpdate = true; }); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + if (parseInt(three.REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -391,12 +399,32 @@ class UnitsUtils { static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; +UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -674,7 +702,48 @@ class MapSphereNodeGeometry extends three.BufferGeometry { class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new three.MeshBasicMaterial({ wireframe: false })); + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(three.REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(three.REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new three.Vector4(...bounds); + const material = new three.ShaderMaterial({ + uniforms: { uTexture: { value: new three.Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -694,12 +763,28 @@ class MapSphereNode extends MapNode { const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new three.TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(three.REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -854,7 +939,11 @@ class LODRaycast { for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; @@ -1305,7 +1394,7 @@ MapMartiniHeightNode.tileSize = 256; class MapView extends three.Mesh { constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0 })); + super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); this.lod = null; this.provider = null; this.heightProvider = null; @@ -1592,7 +1681,7 @@ class GoogleMapsProvider extends MapProvider { this.createSession(); } createSession() { - const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; + const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; const data = JSON.stringify({ mapType: this.mapType, language: 'en-EN', @@ -1601,7 +1690,7 @@ class GoogleMapsProvider extends MapProvider { overlay: this.overlay, scale: 'scaleFactor1x' }); - XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { this.sessionToken = response.session; }, function (xhr) { throw new Error('Unable to create a google maps session.'); @@ -1617,7 +1706,7 @@ class GoogleMapsProvider extends MapProvider { reject(); }; image.crossOrigin = 'Anonymous'; - image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; }); } } diff --git a/build/geo-three.js b/build/geo-three.js index 7fa15dc..be97347 100644 --- a/build/geo-three.js +++ b/build/geo-three.js @@ -29,740 +29,825 @@ }); } - class MapProvider { - constructor() { - this.name = ''; - this.minZoom = 0; - this.maxZoom = 20; - this.bounds = []; - this.center = []; - } - fetchTile(zoom, x, y) { - return null; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } + class MapProvider { + constructor() { + this.name = ''; + this.minZoom = 0; + this.maxZoom = 20; + this.bounds = []; + this.center = []; + } + fetchTile(zoom, x, y) { + return null; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } } - class OpenStreetMapsProvider extends MapProvider { - constructor(address = 'https://a.tile.openstreetmap.org/') { - super(); - this.address = address; - this.format = 'png'; - this.maxZoom = 19; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } + class OpenStreetMapsProvider extends MapProvider { + constructor(address = 'https://a.tile.openstreetmap.org/') { + super(); + this.address = address; + this.format = 'png'; + this.maxZoom = 19; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } - class CanvasUtils { - static createOffscreenCanvas(width, height) { - if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(width, height); - } - else { - let canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - } + class CanvasUtils { + static createOffscreenCanvas(width, height) { + if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(width, height); + } + else { + let canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + } + } } - class TextureUtils { - static createFillTexture(color = '#000000', width = 1, height = 1) { - const canvas = CanvasUtils.createOffscreenCanvas(width, height); - const context = canvas.getContext('2d'); - context.fillStyle = color; - context.fillRect(0, 0, width, height); - const texture = new three.Texture(canvas); - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.generateMipmaps = false; - texture.needsUpdate = true; - return texture; - } + class TextureUtils { + static createFillTexture(color = '#000000', width = 1, height = 1) { + const canvas = CanvasUtils.createOffscreenCanvas(width, height); + const context = canvas.getContext('2d'); + context.fillStyle = color; + context.fillRect(0, 0, width, height); + const texture = new three.Texture(canvas); + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + return texture; + } } - class QuadTreePosition { - } - QuadTreePosition.root = -1; - QuadTreePosition.topLeft = 0; - QuadTreePosition.topRight = 1; - QuadTreePosition.bottomLeft = 2; - QuadTreePosition.bottomRight = 3; - class MapNode extends three.Mesh { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { - super(geometry, material); - this.mapView = null; - this.parentNode = null; - this.subdivided = false; - this.disposed = false; - this.nodesLoaded = 0; - this.childrenCache = null; - this.isMesh = true; - this.mapView = mapView; - this.parentNode = parentNode; - this.disposed = false; - this.location = location; - this.level = level; - this.x = x; - this.y = y; - this.initialize(); - } - initialize() { - return __awaiter(this, void 0, void 0, function* () { }); - } - createChildNodes() { } - subdivide() { - const maxZoom = this.mapView.maxZoom(); - if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { - return; - } - if (this.mapView.cacheTiles && this.childrenCache !== null) { - this.isMesh = false; - this.children = this.childrenCache; - this.nodesLoaded = this.childrenCache.length; - } - else { - this.createChildNodes(); - } - this.subdivided = true; - } - simplify() { - const minZoom = this.mapView.minZoom(); - if (this.level - 1 < minZoom) { - return; - } - if (this.mapView.cacheTiles) { - this.childrenCache = this.children; - } - else { - for (let i = 0; i < this.children.length; i++) { - this.children[i].dispose(); - } - } - this.subdivided = false; - this.isMesh = true; - this.children = []; - this.nodesLoaded = 0; - } - loadData() { - return __awaiter(this, void 0, void 0, function* () { - if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapNode.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.warn('Geo-Three: Failed to load node tile data.', this); - this.material.map = MapNode.defaultTexture; - } - this.material.needsUpdate = true; - }); - } - nodeReady() { - if (this.disposed) { - console.warn('Geo-Three: nodeReady() called for disposed node.', this); - this.dispose(); - return; - } - if (this.parentNode !== null) { - this.parentNode.nodesLoaded++; - if (this.parentNode.nodesLoaded === MapNode.childrens) { - if (this.parentNode.subdivided === true) { - this.parentNode.isMesh = false; - } - for (let i = 0; i < this.parentNode.children.length; i++) { - this.parentNode.children[i].visible = true; - } - } - if (this.parentNode.nodesLoaded > MapNode.childrens) { - console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); - } - } - else { - this.visible = true; - } - } - dispose() { - this.disposed = true; - const self = this; - try { - const material = self.material; - material.dispose(); - if (material.map && material.map !== MapNode.defaultTexture) { - material.map.dispose(); - } - } - catch (e) { } - try { - self.geometry.dispose(); - } - catch (e) { } - } - } - MapNode.defaultTexture = TextureUtils.createFillTexture(); - MapNode.baseGeometry = null; - MapNode.baseScale = null; + class QuadTreePosition { + } + QuadTreePosition.root = -1; + QuadTreePosition.topLeft = 0; + QuadTreePosition.topRight = 1; + QuadTreePosition.bottomLeft = 2; + QuadTreePosition.bottomRight = 3; + class MapNode extends three.Mesh { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { + super(geometry, material); + this.mapView = null; + this.parentNode = null; + this.subdivided = false; + this.disposed = false; + this.nodesLoaded = 0; + this.childrenCache = null; + this.isMesh = true; + this.mapView = mapView; + this.parentNode = parentNode; + this.disposed = false; + this.location = location; + this.level = level; + this.x = x; + this.y = y; + this.initialize(); + } + initialize() { + return __awaiter(this, void 0, void 0, function* () { }); + } + createChildNodes() { } + subdivide() { + const maxZoom = this.mapView.maxZoom(); + if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { + return; + } + if (this.mapView.cacheTiles && this.childrenCache !== null) { + this.isMesh = false; + this.children = this.childrenCache; + this.nodesLoaded = this.childrenCache.length; + } + else { + this.createChildNodes(); + } + this.subdivided = true; + } + simplify() { + const minZoom = this.mapView.minZoom(); + if (this.level - 1 < minZoom) { + return; + } + if (this.mapView.cacheTiles) { + this.childrenCache = this.children; + } + else { + for (let i = 0; i < this.children.length; i++) { + this.children[i].dispose(); + } + } + this.subdivided = false; + this.isMesh = true; + this.children = []; + this.nodesLoaded = 0; + } + loadData() { + return __awaiter(this, void 0, void 0, function* () { + if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapNode.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); + yield this.applyTexture(image); + } + catch (e) { + if (this.disposed) { + return; + } + console.warn('Geo-Three: Failed to load node tile data.', this); + this.material.map = MapNode.defaultTexture; + } + this.material.needsUpdate = true; + }); + } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + if (parseInt(three.REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } + nodeReady() { + if (this.disposed) { + console.warn('Geo-Three: nodeReady() called for disposed node.', this); + this.dispose(); + return; + } + if (this.parentNode !== null) { + this.parentNode.nodesLoaded++; + if (this.parentNode.nodesLoaded === MapNode.childrens) { + if (this.parentNode.subdivided === true) { + this.parentNode.isMesh = false; + } + for (let i = 0; i < this.parentNode.children.length; i++) { + this.parentNode.children[i].visible = true; + } + } + if (this.parentNode.nodesLoaded > MapNode.childrens) { + console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); + } + } + else { + this.visible = true; + } + } + dispose() { + this.disposed = true; + const self = this; + try { + const material = self.material; + material.dispose(); + if (material.map && material.map !== MapNode.defaultTexture) { + material.map.dispose(); + } + } + catch (e) { } + try { + self.geometry.dispose(); + } + catch (e) { } + } + } + MapNode.defaultTexture = TextureUtils.createFillTexture(); + MapNode.baseGeometry = null; + MapNode.baseScale = null; MapNode.childrens = 4; - class MapNodeGeometry extends three.BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - } - static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - vertices.push(x, 0, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1 - iz / heightSegments); - } - } - for (let iz = 0; iz < heightSegments; iz++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix + gridX * iz; - const b = ix + gridX * (iz + 1); - const c = ix + 1 + gridX * (iz + 1); - const d = ix + 1 + gridX * iz; - indices.push(a, b, d, b, c, d); - } - } - } - static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - let start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = -heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1); - } - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix; - const d = ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(d, b, a, d, c, b); - } - start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = heightSegments * segmentHeight - heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 0); - } - let offset = gridX * gridZ - widthSegments - 1; - for (let ix = 0; ix < widthSegments; ix++) { - const a = offset + ix; - const d = offset + ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = -widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ; - const d = (iz + 1) * gridZ; - const b = iz + start; - const c = iz + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = widthSegments * segmentWidth - widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(1.0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ + heightSegments; - const d = (iz + 1) * gridZ + heightSegments; - const b = iz + start; - const c = iz + start + 1; - indices.push(d, b, a, d, c, b); - } - } + class MapNodeGeometry extends three.BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + } + static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + vertices.push(x, 0, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1 - iz / heightSegments); + } + } + for (let iz = 0; iz < heightSegments; iz++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix + gridX * iz; + const b = ix + gridX * (iz + 1); + const c = ix + 1 + gridX * (iz + 1); + const d = ix + 1 + gridX * iz; + indices.push(a, b, d, b, c, d); + } + } + } + static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + let start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = -heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1); + } + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix; + const d = ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(d, b, a, d, c, b); + } + start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = heightSegments * segmentHeight - heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 0); + } + let offset = gridX * gridZ - widthSegments - 1; + for (let ix = 0; ix < widthSegments; ix++) { + const a = offset + ix; + const d = offset + ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = -widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ; + const d = (iz + 1) * gridZ; + const b = iz + start; + const c = iz + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = widthSegments * segmentWidth - widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(1.0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ + heightSegments; + const d = (iz + 1) * gridZ + heightSegments; + const b = iz + start; + const c = iz + start + 1; + indices.push(d, b, a, d, c, b); + } + } } - class Geolocation { - constructor(latitude, longitude) { - this.latitude = latitude; - this.longitude = longitude; - } + class Geolocation { + constructor(latitude, longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } - class UnitsUtils { - static datumsToSpherical(latitude, longitude) { - const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; - let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); - y = y * UnitsUtils.EARTH_ORIGIN / 180.0; - return new three.Vector2(x, y); - } - static sphericalToDatums(x, y) { - const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; - let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; - latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); - return new Geolocation(latitude, longitude); - } - static quadtreeToDatums(zoom, x, y) { - const n = Math.pow(2.0, zoom); - const longitude = x / n * 360.0 - 180.0; - const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); - const latitude = 180.0 * (latitudeRad / Math.PI); - return new Geolocation(latitude, longitude); - } - static vectorToDatums(dir) { - const radToDeg = 180 / Math.PI; - const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; - const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; - return new Geolocation(latitude, longitude); - } - static datumsToVector(latitude, longitude) { - const degToRad = Math.PI / 180; - const rotX = longitude * degToRad; - const rotY = latitude * degToRad; - var cos = Math.cos(rotY); - return new three.Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); - } - static mapboxAltitude(color) { - return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; - } - } - UnitsUtils.EARTH_RADIUS = 6371008; - UnitsUtils.EARTH_RADIUS_A = 6378137.0; - UnitsUtils.EARTH_RADIUS_B = 6356752.314245; - UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; + class UnitsUtils { + static datumsToSpherical(latitude, longitude) { + const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; + let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); + y = y * UnitsUtils.EARTH_ORIGIN / 180.0; + return new three.Vector2(x, y); + } + static sphericalToDatums(x, y) { + const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; + let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; + latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); + return new Geolocation(latitude, longitude); + } + static quadtreeToDatums(zoom, x, y) { + const n = Math.pow(2.0, zoom); + const longitude = x / n * 360.0 - 180.0; + const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); + const latitude = 180.0 * (latitudeRad / Math.PI); + return new Geolocation(latitude, longitude); + } + static vectorToDatums(dir) { + const radToDeg = 180 / Math.PI; + const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; + const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; + return new Geolocation(latitude, longitude); + } + static datumsToVector(latitude, longitude) { + const degToRad = Math.PI / 180; + const rotX = longitude * degToRad; + const rotY = latitude * degToRad; + var cos = Math.cos(rotY); + return new three.Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); + } + static mapboxAltitude(color) { + return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; + } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } + } + UnitsUtils.EARTH_RADIUS = 6371008; + UnitsUtils.EARTH_RADIUS_A = 6378137.0; + UnitsUtils.EARTH_RADIUS_B = 6356752.314245; + UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; + UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; - class MapPlaneNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new three.MeshBasicMaterial({ wireframe: false })); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); - MapPlaneNode.baseGeometry = MapPlaneNode.geometry; + class MapPlaneNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new three.MeshBasicMaterial({ wireframe: false })); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); + MapPlaneNode.baseGeometry = MapPlaneNode.geometry; MapPlaneNode.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1.0, UnitsUtils.EARTH_PERIMETER); - class MapNodeHeightGeometry extends three.BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - const data = imageData.data; - for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - vertices[j + 1] = value; - } - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - if (calculateNormals) { - this.computeNormals(widthSegments, heightSegments); - } - } - computeNormals(widthSegments, heightSegments) { - const positionAttribute = this.getAttribute('position'); - if (positionAttribute !== undefined) { - let normalAttribute = this.getAttribute('normal'); - const normalLength = heightSegments * widthSegments; - for (let i = 0; i < normalLength; i++) { - normalAttribute.setXYZ(i, 0, 0, 0); - } - const pA = new three.Vector3(), pB = new three.Vector3(), pC = new three.Vector3(); - const nA = new three.Vector3(), nB = new three.Vector3(), nC = new three.Vector3(); - const cb = new three.Vector3(), ab = new three.Vector3(); - const indexLength = heightSegments * widthSegments * 6; - for (let i = 0; i < indexLength; i += 3) { - const vA = this.index.getX(i + 0); - const vB = this.index.getX(i + 1); - const vC = this.index.getX(i + 2); - pA.fromBufferAttribute(positionAttribute, vA); - pB.fromBufferAttribute(positionAttribute, vB); - pC.fromBufferAttribute(positionAttribute, vC); - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - nA.fromBufferAttribute(normalAttribute, vA); - nB.fromBufferAttribute(normalAttribute, vB); - nC.fromBufferAttribute(normalAttribute, vC); - nA.add(cb); - nB.add(cb); - nC.add(cb); - normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); - normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); - normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); - } - this.normalizeNormals(); - normalAttribute.needsUpdate = true; - } - } + class MapNodeHeightGeometry extends three.BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + const data = imageData.data; + for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + vertices[j + 1] = value; + } + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + if (calculateNormals) { + this.computeNormals(widthSegments, heightSegments); + } + } + computeNormals(widthSegments, heightSegments) { + const positionAttribute = this.getAttribute('position'); + if (positionAttribute !== undefined) { + let normalAttribute = this.getAttribute('normal'); + const normalLength = heightSegments * widthSegments; + for (let i = 0; i < normalLength; i++) { + normalAttribute.setXYZ(i, 0, 0, 0); + } + const pA = new three.Vector3(), pB = new three.Vector3(), pC = new three.Vector3(); + const nA = new three.Vector3(), nB = new three.Vector3(), nC = new three.Vector3(); + const cb = new three.Vector3(), ab = new three.Vector3(); + const indexLength = heightSegments * widthSegments * 6; + for (let i = 0; i < indexLength; i += 3) { + const vA = this.index.getX(i + 0); + const vB = this.index.getX(i + 1); + const vC = this.index.getX(i + 2); + pA.fromBufferAttribute(positionAttribute, vA); + pB.fromBufferAttribute(positionAttribute, vB); + pC.fromBufferAttribute(positionAttribute, vC); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + nA.fromBufferAttribute(normalAttribute, vA); + nB.fromBufferAttribute(normalAttribute, vB); + nC.fromBufferAttribute(normalAttribute, vC); + nA.add(cb); + nB.add(cb); + nC.add(cb); + normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); + normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); + normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); + } + this.normalizeNormals(); + normalAttribute.needsUpdate = true; + } + } } - class MapHeightNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new three.MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { - super(parentNode, mapView, location, level, x, y, geometry, material); - this.heightLoaded = false; - this.textureLoaded = false; - this.geometrySize = 16; - this.geometryNormals = false; - this.isMesh = true; - this.visible = false; - this.matrixAutoUpdate = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - yield this.loadHeightGeometry(); - this.nodeReady(); - }); - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.geometry = MapPlaneNode.baseGeometry; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); - const context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); - const imageData = context.getImageData(0, 0, canvas.width, canvas.height); - this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); - } - catch (e) { - if (this.disposed) { - return; - } - this.geometry = MapPlaneNode.baseGeometry; - } - this.heightLoaded = true; - }); - } - createChildNodes() { - const level = this.level + 1; - const Constructor = Object.getPrototypeOf(this).constructor; - const x = this.x * 2; - const y = this.y * 2; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapHeightNode.tileSize = 256; - MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); - MapHeightNode.baseGeometry = MapPlaneNode.geometry; + class MapHeightNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new three.MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { + super(parentNode, mapView, location, level, x, y, geometry, material); + this.heightLoaded = false; + this.textureLoaded = false; + this.geometrySize = 16; + this.geometryNormals = false; + this.isMesh = true; + this.visible = false; + this.matrixAutoUpdate = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + yield this.loadHeightGeometry(); + this.nodeReady(); + }); + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.geometry = MapPlaneNode.baseGeometry; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); + } + catch (e) { + if (this.disposed) { + return; + } + this.geometry = MapPlaneNode.baseGeometry; + } + this.heightLoaded = true; + }); + } + createChildNodes() { + const level = this.level + 1; + const Constructor = Object.getPrototypeOf(this).constructor; + const x = this.x * 2; + const y = this.y * 2; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapHeightNode.tileSize = 256; + MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + MapHeightNode.baseGeometry = MapPlaneNode.geometry; MapHeightNode.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); - class MapSphereNodeGeometry extends three.BufferGeometry { - constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { - super(); - const thetaEnd = thetaStart + thetaLength; - let index = 0; - const grid = []; - const vertex = new three.Vector3(); - const normal = new three.Vector3(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - for (let iy = 0; iy <= heightSegments; iy++) { - const verticesRow = []; - const v = iy / heightSegments; - for (let ix = 0; ix <= widthSegments; ix++) { - const u = ix / widthSegments; - vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertex.y = radius * Math.cos(thetaStart + v * thetaLength); - vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertices.push(vertex.x, vertex.y, vertex.z); - normal.set(vertex.x, vertex.y, vertex.z).normalize(); - normals.push(normal.x, normal.y, normal.z); - uvs.push(u, 1 - v); - verticesRow.push(index++); - } - grid.push(verticesRow); - } - for (let iy = 0; iy < heightSegments; iy++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = grid[iy][ix + 1]; - const b = grid[iy][ix]; - const c = grid[iy + 1][ix]; - const d = grid[iy + 1][ix + 1]; - if (iy !== 0 || thetaStart > 0) { - indices.push(a, b, d); - } - if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { - indices.push(b, c, d); - } - } - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - } + class MapSphereNodeGeometry extends three.BufferGeometry { + constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { + super(); + const thetaEnd = thetaStart + thetaLength; + let index = 0; + const grid = []; + const vertex = new three.Vector3(); + const normal = new three.Vector3(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + for (let iy = 0; iy <= heightSegments; iy++) { + const verticesRow = []; + const v = iy / heightSegments; + for (let ix = 0; ix <= widthSegments; ix++) { + const u = ix / widthSegments; + vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertex.y = radius * Math.cos(thetaStart + v * thetaLength); + vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertices.push(vertex.x, vertex.y, vertex.z); + normal.set(vertex.x, vertex.y, vertex.z).normalize(); + normals.push(normal.x, normal.y, normal.z); + uvs.push(u, 1 - v); + verticesRow.push(index++); + } + grid.push(verticesRow); + } + for (let iy = 0; iy < heightSegments; iy++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = grid[iy][ix + 1]; + const b = grid[iy][ix]; + const c = grid[iy + 1][ix]; + const d = grid[iy + 1][ix + 1]; + if (iy !== 0 || thetaStart > 0) { + indices.push(a, b, d); + } + if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { + indices.push(b, c, d); + } + } + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + } } - class MapSphereNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new three.MeshBasicMaterial({ wireframe: false })); - this.applyScaleNode(); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - static createGeometry(zoom, x, y) { - const range = Math.pow(2, zoom); - const max = 40; - const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; - return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); - } - applyScaleNode() { - this.geometry.computeBoundingBox(); - const box = this.geometry.boundingBox.clone(); - const center = box.getCenter(new three.Vector3()); - const matrix = new three.Matrix4(); - matrix.compose(new three.Vector3(-center.x, -center.y, -center.z), new three.Quaternion(), new three.Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); - this.geometry.applyMatrix4(matrix); - this.position.copy(center); - this.updateMatrix(); - this.updateMatrixWorld(); - } - updateMatrix() { - this.matrix.setPosition(this.position); - this.matrixWorldNeedsUpdate = true; - } - updateMatrixWorld(force = false) { - if (this.matrixWorldNeedsUpdate || force) { - this.matrixWorld.copy(this.matrix); - this.matrixWorldNeedsUpdate = false; - } - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - this.add(node); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); - MapSphereNode.baseScale = new three.Vector3(1, 1, 1); + class MapSphereNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(three.REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(three.REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new three.Vector4(...bounds); + const material = new three.ShaderMaterial({ + uniforms: { uTexture: { value: new three.Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + this.applyScaleNode(); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + static createGeometry(zoom, x, y) { + const range = Math.pow(2, zoom); + const max = 40; + const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); + return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); + } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new three.TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(three.REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } + applyScaleNode() { + this.geometry.computeBoundingBox(); + const box = this.geometry.boundingBox.clone(); + const center = box.getCenter(new three.Vector3()); + const matrix = new three.Matrix4(); + matrix.compose(new three.Vector3(-center.x, -center.y, -center.z), new three.Quaternion(), new three.Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); + this.geometry.applyMatrix4(matrix); + this.position.copy(center); + this.updateMatrix(); + this.updateMatrixWorld(); + } + updateMatrix() { + this.matrix.setPosition(this.position); + this.matrixWorldNeedsUpdate = true; + } + updateMatrixWorld(force = false) { + if (this.matrixWorldNeedsUpdate || force) { + this.matrixWorld.copy(this.matrix); + this.matrixWorldNeedsUpdate = false; + } + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + this.add(node); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); + MapSphereNode.baseScale = new three.Vector3(1, 1, 1); MapSphereNode.segments = 80; - class MapHeightNodeShader extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - const material = MapHeightNodeShader.prepareMaterial(new three.MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); - super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); - this.frustumCulled = false; - } - static prepareMaterial(material) { - material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; - material.onBeforeCompile = (shader) => { - for (const i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = + class MapHeightNodeShader extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + const material = MapHeightNodeShader.prepareMaterial(new three.MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); + super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); + this.frustumCulled = false; + } + static prepareMaterial(material) { + material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; + material.onBeforeCompile = (shader) => { + for (const i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform sampler2D heightMap; - ` + shader.vertexShader; + ` + shader.vertexShader; shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -773,389 +858,393 @@ // Vertex position based on height gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0); - `); - }; - return material; - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapHeightNodeShader.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.NearestFilter; - texture.minFilter = three.NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.error('Geo-Three: Failed to load node tile height data.', this); - this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; - } - this.material.needsUpdate = true; - this.heightLoaded = true; - }); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - this.geometry = MapPlaneNode.geometry; - super.raycast(raycaster, intersects); - this.geometry = MapHeightNodeShader.geometry; - } - } - dispose() { - super.dispose(); - if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { - this.material.userData.heightMap.value.dispose(); - } - } - } - MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); - MapHeightNodeShader.geometrySize = 256; - MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); - MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; + `); + }; + return material; + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapHeightNodeShader.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.NearestFilter; + texture.minFilter = three.NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.error('Geo-Three: Failed to load node tile height data.', this); + this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; + } + this.material.needsUpdate = true; + this.heightLoaded = true; + }); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + this.geometry = MapPlaneNode.geometry; + super.raycast(raycaster, intersects); + this.geometry = MapHeightNodeShader.geometry; + } + } + dispose() { + super.dispose(); + if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { + this.material.userData.heightMap.value.dispose(); + } + } + } + MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); + MapHeightNodeShader.geometrySize = 256; + MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); + MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; MapHeightNodeShader.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); - class LODRaycast { - constructor() { - this.subdivisionRays = 1; - this.thresholdUp = 0.6; - this.thresholdDown = 0.15; - this.raycaster = new three.Raycaster(); - this.mouse = new three.Vector2(); - this.powerDistance = false; - this.scaleDistance = true; - } - updateLOD(view, camera, renderer, scene) { - const intersects = []; - for (let t = 0; t < this.subdivisionRays; t++) { - this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); - this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); - } - for (let i = 0; i < intersects.length; i++) { - const node = intersects[i].object; - let distance = intersects[i].distance; - if (this.powerDistance) { - distance = Math.pow(distance * 2, node.level); - } - if (this.scaleDistance) { - const matrix = node.matrixWorld.elements; - const vector = new three.Vector3(matrix[0], matrix[1], matrix[2]); - distance = vector.length() / distance; - } - if (distance > this.thresholdUp) { - node.subdivide(); - } - else if (distance < this.thresholdDown && node.parentNode) { - node.parentNode.simplify(); - } - } - } + class LODRaycast { + constructor() { + this.subdivisionRays = 1; + this.thresholdUp = 0.6; + this.thresholdDown = 0.15; + this.raycaster = new three.Raycaster(); + this.mouse = new three.Vector2(); + this.powerDistance = false; + this.scaleDistance = true; + } + updateLOD(view, camera, renderer, scene) { + const intersects = []; + for (let t = 0; t < this.subdivisionRays; t++) { + this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); + this.raycaster.setFromCamera(this.mouse, camera); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } + } + for (let i = 0; i < intersects.length; i++) { + const node = intersects[i].object; + let distance = intersects[i].distance; + if (this.powerDistance) { + distance = Math.pow(distance * 2, node.level); + } + if (this.scaleDistance) { + const matrix = node.matrixWorld.elements; + const vector = new three.Vector3(matrix[0], matrix[1], matrix[2]); + distance = vector.length() / distance; + } + if (distance > this.thresholdUp) { + node.subdivide(); + } + else if (distance < this.thresholdDown && node.parentNode) { + node.parentNode.simplify(); + } + } + } } - class Martini { - constructor(gridSize = 257) { - this.gridSize = gridSize; - const tileSize = gridSize - 1; - if (tileSize & tileSize - 1) { - throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); - } - this.numTriangles = tileSize * tileSize * 2 - 2; - this.numParentTriangles = this.numTriangles - tileSize * tileSize; - this.indices = new Uint32Array(this.gridSize * this.gridSize); - this.coords = new Uint16Array(this.numTriangles * 4); - for (let i = 0; i < this.numTriangles; i++) { - let id = i + 2; - let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; - if (id & 1) { - bx = by = cx = tileSize; - } - else { - ax = ay = cy = tileSize; - } - while ((id >>= 1) > 1) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (id & 1) { - bx = ax; - by = ay; - ax = cx; - ay = cy; - } - else { - ax = bx; - ay = by; - bx = cx; - by = cy; - } - cx = mx; - cy = my; - } - const k = i * 4; - this.coords[k + 0] = ax; - this.coords[k + 1] = ay; - this.coords[k + 2] = bx; - this.coords[k + 3] = by; - } - } - createTile(terrain) { - return new Tile(terrain, this); - } - } - class Tile { - constructor(terrain, martini) { - const size = martini.gridSize; - if (terrain.length !== size * size) { - throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); - } - this.terrain = terrain; - this.martini = martini; - this.errors = new Float32Array(terrain.length); - this.update(); - } - update() { - const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; - const { terrain, errors } = this; - for (let i = numTriangles - 1; i >= 0; i--) { - const k = i * 4; - const ax = coords[k + 0]; - const ay = coords[k + 1]; - const bx = coords[k + 2]; - const by = coords[k + 3]; - const mx = ax + bx >> 1; - const my = ay + by >> 1; - const cx = mx + my - ay; - const cy = my + ax - mx; - const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; - const middleIndex = my * size + mx; - const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); - errors[middleIndex] = Math.max(errors[middleIndex], middleError); - if (i < numParentTriangles) { - const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); - const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); - errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); - } - } - } - getMesh(maxError = 0, withSkirts = false) { - const { gridSize: size, indices } = this.martini; - const { errors } = this; - let numVertices = 0; - let numTriangles = 0; - const max = size - 1; - let aIndex, bIndex, cIndex = 0; - const leftSkirtIndices = []; - const rightSkirtIndices = []; - const bottomSkirtIndices = []; - const topSkirtIndices = []; - indices.fill(0); - function countElements(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - countElements(cx, cy, ax, ay, mx, my); - countElements(bx, by, cx, cy, mx, my); - } - else { - aIndex = ay * size + ax; - bIndex = by * size + bx; - cIndex = cy * size + cx; - if (indices[aIndex] === 0) { - if (withSkirts) { - if (ax === 0) { - leftSkirtIndices.push(numVertices); - } - else if (ax === max) { - rightSkirtIndices.push(numVertices); - } - if (ay === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (ay === max) { - topSkirtIndices.push(numVertices); - } - } - indices[aIndex] = ++numVertices; - } - if (indices[bIndex] === 0) { - if (withSkirts) { - if (bx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (bx === max) { - rightSkirtIndices.push(numVertices); - } - if (by === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (by === max) { - topSkirtIndices.push(numVertices); - } - } - indices[bIndex] = ++numVertices; - } - if (indices[cIndex] === 0) { - if (withSkirts) { - if (cx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (cx === max) { - rightSkirtIndices.push(numVertices); - } - if (cy === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (cy === max) { - topSkirtIndices.push(numVertices); - } - } - indices[cIndex] = ++numVertices; - } - numTriangles++; - } - } - countElements(0, 0, max, max, max, 0); - countElements(max, max, 0, 0, 0, max); - let numTotalVertices = numVertices * 2; - let numTotalTriangles = numTriangles * 3; - if (withSkirts) { - numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; - numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; - } - const vertices = new Uint16Array(numTotalVertices); - const triangles = new Uint32Array(numTotalTriangles); - let triIndex = 0; - function processTriangle(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - processTriangle(cx, cy, ax, ay, mx, my); - processTriangle(bx, by, cx, cy, mx, my); - } - else { - const a = indices[ay * size + ax] - 1; - const b = indices[by * size + bx] - 1; - const c = indices[cy * size + cx] - 1; - vertices[2 * a] = ax; - vertices[2 * a + 1] = ay; - vertices[2 * b] = bx; - vertices[2 * b + 1] = by; - vertices[2 * c] = cx; - vertices[2 * c + 1] = cy; - triangles[triIndex++] = a; - triangles[triIndex++] = b; - triangles[triIndex++] = c; - } - } - processTriangle(0, 0, max, max, max, 0); - processTriangle(max, max, 0, 0, 0, max); - if (withSkirts) { - leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); - rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); - bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); - topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); - let skirtIndex = numVertices * 2; - function constructSkirt(skirt) { - const skirtLength = skirt.length; - for (let i = 0; i < skirtLength - 1; i++) { - const currIndex = skirt[i]; - const nextIndex = skirt[i + 1]; - const currentSkirt = skirtIndex / 2; - const nextSkirt = (skirtIndex + 2) / 2; - vertices[skirtIndex++] = vertices[2 * currIndex]; - vertices[skirtIndex++] = vertices[2 * currIndex + 1]; - triangles[triIndex++] = currIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextSkirt; - triangles[triIndex++] = nextIndex; - } - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; - } - constructSkirt(leftSkirtIndices); - constructSkirt(rightSkirtIndices); - constructSkirt(bottomSkirtIndices); - constructSkirt(topSkirtIndices); - } - return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; - } + class Martini { + constructor(gridSize = 257) { + this.gridSize = gridSize; + const tileSize = gridSize - 1; + if (tileSize & tileSize - 1) { + throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); + } + this.numTriangles = tileSize * tileSize * 2 - 2; + this.numParentTriangles = this.numTriangles - tileSize * tileSize; + this.indices = new Uint32Array(this.gridSize * this.gridSize); + this.coords = new Uint16Array(this.numTriangles * 4); + for (let i = 0; i < this.numTriangles; i++) { + let id = i + 2; + let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; + if (id & 1) { + bx = by = cx = tileSize; + } + else { + ax = ay = cy = tileSize; + } + while ((id >>= 1) > 1) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (id & 1) { + bx = ax; + by = ay; + ax = cx; + ay = cy; + } + else { + ax = bx; + ay = by; + bx = cx; + by = cy; + } + cx = mx; + cy = my; + } + const k = i * 4; + this.coords[k + 0] = ax; + this.coords[k + 1] = ay; + this.coords[k + 2] = bx; + this.coords[k + 3] = by; + } + } + createTile(terrain) { + return new Tile(terrain, this); + } + } + class Tile { + constructor(terrain, martini) { + const size = martini.gridSize; + if (terrain.length !== size * size) { + throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); + } + this.terrain = terrain; + this.martini = martini; + this.errors = new Float32Array(terrain.length); + this.update(); + } + update() { + const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; + const { terrain, errors } = this; + for (let i = numTriangles - 1; i >= 0; i--) { + const k = i * 4; + const ax = coords[k + 0]; + const ay = coords[k + 1]; + const bx = coords[k + 2]; + const by = coords[k + 3]; + const mx = ax + bx >> 1; + const my = ay + by >> 1; + const cx = mx + my - ay; + const cy = my + ax - mx; + const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; + const middleIndex = my * size + mx; + const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); + errors[middleIndex] = Math.max(errors[middleIndex], middleError); + if (i < numParentTriangles) { + const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); + const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); + errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); + } + } + } + getMesh(maxError = 0, withSkirts = false) { + const { gridSize: size, indices } = this.martini; + const { errors } = this; + let numVertices = 0; + let numTriangles = 0; + const max = size - 1; + let aIndex, bIndex, cIndex = 0; + const leftSkirtIndices = []; + const rightSkirtIndices = []; + const bottomSkirtIndices = []; + const topSkirtIndices = []; + indices.fill(0); + function countElements(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + countElements(cx, cy, ax, ay, mx, my); + countElements(bx, by, cx, cy, mx, my); + } + else { + aIndex = ay * size + ax; + bIndex = by * size + bx; + cIndex = cy * size + cx; + if (indices[aIndex] === 0) { + if (withSkirts) { + if (ax === 0) { + leftSkirtIndices.push(numVertices); + } + else if (ax === max) { + rightSkirtIndices.push(numVertices); + } + if (ay === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (ay === max) { + topSkirtIndices.push(numVertices); + } + } + indices[aIndex] = ++numVertices; + } + if (indices[bIndex] === 0) { + if (withSkirts) { + if (bx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (bx === max) { + rightSkirtIndices.push(numVertices); + } + if (by === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (by === max) { + topSkirtIndices.push(numVertices); + } + } + indices[bIndex] = ++numVertices; + } + if (indices[cIndex] === 0) { + if (withSkirts) { + if (cx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (cx === max) { + rightSkirtIndices.push(numVertices); + } + if (cy === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (cy === max) { + topSkirtIndices.push(numVertices); + } + } + indices[cIndex] = ++numVertices; + } + numTriangles++; + } + } + countElements(0, 0, max, max, max, 0); + countElements(max, max, 0, 0, 0, max); + let numTotalVertices = numVertices * 2; + let numTotalTriangles = numTriangles * 3; + if (withSkirts) { + numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; + numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; + } + const vertices = new Uint16Array(numTotalVertices); + const triangles = new Uint32Array(numTotalTriangles); + let triIndex = 0; + function processTriangle(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + processTriangle(cx, cy, ax, ay, mx, my); + processTriangle(bx, by, cx, cy, mx, my); + } + else { + const a = indices[ay * size + ax] - 1; + const b = indices[by * size + bx] - 1; + const c = indices[cy * size + cx] - 1; + vertices[2 * a] = ax; + vertices[2 * a + 1] = ay; + vertices[2 * b] = bx; + vertices[2 * b + 1] = by; + vertices[2 * c] = cx; + vertices[2 * c + 1] = cy; + triangles[triIndex++] = a; + triangles[triIndex++] = b; + triangles[triIndex++] = c; + } + } + processTriangle(0, 0, max, max, max, 0); + processTriangle(max, max, 0, 0, 0, max); + if (withSkirts) { + leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); + rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); + bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); + topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); + let skirtIndex = numVertices * 2; + function constructSkirt(skirt) { + const skirtLength = skirt.length; + for (let i = 0; i < skirtLength - 1; i++) { + const currIndex = skirt[i]; + const nextIndex = skirt[i + 1]; + const currentSkirt = skirtIndex / 2; + const nextSkirt = (skirtIndex + 2) / 2; + vertices[skirtIndex++] = vertices[2 * currIndex]; + vertices[skirtIndex++] = vertices[2 * currIndex + 1]; + triangles[triIndex++] = currIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextSkirt; + triangles[triIndex++] = nextIndex; + } + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; + } + constructSkirt(leftSkirtIndices); + constructSkirt(rightSkirtIndices); + constructSkirt(bottomSkirtIndices); + constructSkirt(topSkirtIndices); + } + return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; + } } - class MapMartiniHeightNode extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { - super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new three.MeshPhongMaterial({ - map: MapMartiniHeightNode.emptyTexture, - color: 0xFFFFFF, - side: three.DoubleSide - }), level, exageration)); - this.elevationDecoder = { - rScaler: 256, - gScaler: 1, - bScaler: 1 / 256, - offset: -32768 - }; - this.exageration = 1.0; - this.meshMaxError = 10; - if (elevationDecoder) { - this.elevationDecoder = elevationDecoder; - } - this.meshMaxError = meshMaxError; - this.exageration = exageration; - this.frustumCulled = false; - } - static prepareMaterial(material, level, exageration = 1.0) { - material.userData = { - heightMap: { value: MapMartiniHeightNode.emptyTexture }, - drawNormals: { value: 0 }, - drawBlack: { value: 0 }, - zoomlevel: { value: level }, - computeNormals: { value: 1 }, - drawTexture: { value: 1 } - }; - material.onBeforeCompile = (shader) => { - for (let i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = + class MapMartiniHeightNode extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { + super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new three.MeshPhongMaterial({ + map: MapMartiniHeightNode.emptyTexture, + color: 0xFFFFFF, + side: three.DoubleSide + }), level, exageration)); + this.elevationDecoder = { + rScaler: 256, + gScaler: 1, + bScaler: 1 / 256, + offset: -32768 + }; + this.exageration = 1.0; + this.meshMaxError = 10; + if (elevationDecoder) { + this.elevationDecoder = elevationDecoder; + } + this.meshMaxError = meshMaxError; + this.exageration = exageration; + this.frustumCulled = false; + } + static prepareMaterial(material, level, exageration = 1.0) { + material.userData = { + heightMap: { value: MapMartiniHeightNode.emptyTexture }, + drawNormals: { value: 0 }, + drawBlack: { value: 0 }, + zoomlevel: { value: level }, + computeNormals: { value: 1 }, + drawTexture: { value: 1 } + }; + material.onBeforeCompile = (shader) => { + for (let i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform bool computeNormals; uniform float zoomlevel; uniform sampler2D heightMap; - ` + shader.vertexShader; - shader.fragmentShader = + ` + shader.vertexShader; + shader.fragmentShader = ` uniform bool drawNormals; uniform bool drawTexture; uniform bool drawBlack; - ` + shader.fragmentShader; + ` + shader.fragmentShader; shader.fragmentShader = shader.fragmentShader.replace('#include ', ` if(drawBlack) { gl_FragColor = vec4( 0.0,0.0,0.0, 1.0 ); @@ -1163,7 +1252,7 @@ gl_FragColor = vec4( ( 0.5 * vNormal + 0.5 ), 1.0 ); } else if (!drawTexture) { gl_FragColor = vec4( 0.0,0.0,0.0, 0.0 ); - }`); + }`); shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -1206,710 +1295,710 @@ v2.z = (e+ h + i + f) / 4.0; vNormal = (normalize(cross(v2 - v0, v1 - v0))).rbg; } - `); - }; - return material; - } - static getTerrain(imageData, tileSize, elevation) { - const { rScaler, bScaler, gScaler, offset } = elevation; - const gridSize = tileSize + 1; - const terrain = new Float32Array(gridSize * gridSize); - for (let i = 0, y = 0; y < tileSize; y++) { - for (let x = 0; x < tileSize; x++, i++) { - const k = i * 4; - const r = imageData[k + 0]; - const g = imageData[k + 1]; - const b = imageData[k + 2]; - terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; - } - } - for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { - terrain[i] = terrain[i - gridSize]; - } - for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { - terrain[i] = terrain[i - 1]; - } - return terrain; - } - static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { - const gridSize = tileSize + 1; - const numOfVerticies = vertices.length / 2; - const positions = new Float32Array(numOfVerticies * 3); - const texCoords = new Float32Array(numOfVerticies * 2); - const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; - const xScale = (maxX - minX) / tileSize; - const yScale = (maxY - minY) / tileSize; - for (let i = 0; i < numOfVerticies; i++) { - const x = vertices[i * 2]; - const y = vertices[i * 2 + 1]; - const pixelIdx = y * gridSize + x; - positions[3 * i + 0] = x * xScale + minX; - positions[3 * i + 1] = -terrain[pixelIdx] * exageration; - positions[3 * i + 2] = -y * yScale + maxY; - texCoords[2 * i + 0] = x / tileSize; - texCoords[2 * i + 1] = y / tileSize; - } - return { - position: { value: positions, size: 3 }, - uv: { value: texCoords, size: 2 } - }; - } - processHeight(image) { - return __awaiter(this, void 0, void 0, function* () { - const tileSize = image.width; - const gridSize = tileSize + 1; - var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); - var context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); - const martini = new Martini(gridSize); - const tile = martini.createTile(terrain); - const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); - const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); - this.geometry = new three.BufferGeometry(); - this.geometry.setIndex(new three.Uint32BufferAttribute(triangles, 1)); - this.geometry.setAttribute('position', new three.Float32BufferAttribute(attributes.position.value, attributes.position.size)); - this.geometry.setAttribute('uv', new three.Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); - this.geometry.rotateX(Math.PI); - var texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.NearestFilter; - texture.minFilter = three.NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - this.material.map = texture; - this.material.needsUpdate = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - this.processHeight(image); - this.heightLoaded = true; - this.nodeReady(); - }); - } - } - MapMartiniHeightNode.geometrySize = 16; - MapMartiniHeightNode.emptyTexture = new three.Texture(); - MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + `); + }; + return material; + } + static getTerrain(imageData, tileSize, elevation) { + const { rScaler, bScaler, gScaler, offset } = elevation; + const gridSize = tileSize + 1; + const terrain = new Float32Array(gridSize * gridSize); + for (let i = 0, y = 0; y < tileSize; y++) { + for (let x = 0; x < tileSize; x++, i++) { + const k = i * 4; + const r = imageData[k + 0]; + const g = imageData[k + 1]; + const b = imageData[k + 2]; + terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; + } + } + for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { + terrain[i] = terrain[i - gridSize]; + } + for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { + terrain[i] = terrain[i - 1]; + } + return terrain; + } + static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { + const gridSize = tileSize + 1; + const numOfVerticies = vertices.length / 2; + const positions = new Float32Array(numOfVerticies * 3); + const texCoords = new Float32Array(numOfVerticies * 2); + const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; + const xScale = (maxX - minX) / tileSize; + const yScale = (maxY - minY) / tileSize; + for (let i = 0; i < numOfVerticies; i++) { + const x = vertices[i * 2]; + const y = vertices[i * 2 + 1]; + const pixelIdx = y * gridSize + x; + positions[3 * i + 0] = x * xScale + minX; + positions[3 * i + 1] = -terrain[pixelIdx] * exageration; + positions[3 * i + 2] = -y * yScale + maxY; + texCoords[2 * i + 0] = x / tileSize; + texCoords[2 * i + 1] = y / tileSize; + } + return { + position: { value: positions, size: 3 }, + uv: { value: texCoords, size: 2 } + }; + } + processHeight(image) { + return __awaiter(this, void 0, void 0, function* () { + const tileSize = image.width; + const gridSize = tileSize + 1; + var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); + var context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); + var imageData = context.getImageData(0, 0, canvas.width, canvas.height); + var data = imageData.data; + const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); + const martini = new Martini(gridSize); + const tile = martini.createTile(terrain); + const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); + const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); + this.geometry = new three.BufferGeometry(); + this.geometry.setIndex(new three.Uint32BufferAttribute(triangles, 1)); + this.geometry.setAttribute('position', new three.Float32BufferAttribute(attributes.position.value, attributes.position.size)); + this.geometry.setAttribute('uv', new three.Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); + this.geometry.rotateX(Math.PI); + var texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.NearestFilter; + texture.minFilter = three.NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + this.material.map = texture; + this.material.needsUpdate = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + this.processHeight(image); + this.heightLoaded = true; + this.nodeReady(); + }); + } + } + MapMartiniHeightNode.geometrySize = 16; + MapMartiniHeightNode.emptyTexture = new three.Texture(); + MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); MapMartiniHeightNode.tileSize = 256; - class MapView extends three.Mesh { - constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0 })); - this.lod = null; - this.provider = null; - this.heightProvider = null; - this.root = null; - this.cacheTiles = false; - this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { - this.lod.updateLOD(this, camera, renderer, scene); - }; - this.lod = new LODRaycast(); - this.provider = provider; - this.heightProvider = heightProvider; - this.setRoot(root); - this.preSubdivide(); - } - setRoot(root) { - if (typeof root === 'number') { - if (!MapView.mapModes.has(root)) { - throw new Error('Map mode ' + root + ' does is not registered.'); - } - const rootConstructor = MapView.mapModes.get(root); - root = new rootConstructor(null, this); - } - if (this.root !== null) { - this.remove(this.root); - this.root = null; - } - this.root = root; - if (this.root !== null) { - this.geometry = this.root.constructor.baseGeometry; - this.scale.copy(this.root.constructor.baseScale); - this.root.mapView = this; - this.add(this.root); - this.root.initialize(); - } - } - preSubdivide() { - var _a, _b; - function subdivide(node, depth) { - if (depth <= 0) { - return; - } - node.subdivide(); - for (let i = 0; i < node.children.length; i++) { - if (node.children[i] instanceof MapNode) { - const child = node.children[i]; - subdivide(child, depth - 1); - } - } - } - const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - if (minZoom > 0) { - subdivide(this.root, minZoom); - } - } - setProvider(provider) { - if (provider !== this.provider) { - this.provider = provider; - this.clear(); - } - } - setHeightProvider(heightProvider) { - if (heightProvider !== this.heightProvider) { - this.heightProvider = heightProvider; - this.clear(); - } - } - clear() { - this.traverse(function (children) { - if (children.childrenCache) { - children.childrenCache = null; - } - if (children.initialize) { - children.initialize(); - } - }); - return this; - } - minZoom() { - var _a, _b; - return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - } - maxZoom() { - var _a, _b; - return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); - } - getMetaData() { - this.provider.getMetaData(); - } - raycast(raycaster, intersects) { - return false; - } - } - MapView.PLANAR = 200; - MapView.SPHERICAL = 201; - MapView.HEIGHT = 202; - MapView.HEIGHT_SHADER = 203; - MapView.MARTINI = 204; - MapView.mapModes = new Map([ - [MapView.PLANAR, MapPlaneNode], - [MapView.SPHERICAL, MapSphereNode], - [MapView.HEIGHT, MapHeightNode], - [MapView.HEIGHT_SHADER, MapHeightNodeShader], - [MapView.MARTINI, MapMartiniHeightNode] + class MapView extends three.Mesh { + constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { + super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); + this.lod = null; + this.provider = null; + this.heightProvider = null; + this.root = null; + this.cacheTiles = false; + this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { + this.lod.updateLOD(this, camera, renderer, scene); + }; + this.lod = new LODRaycast(); + this.provider = provider; + this.heightProvider = heightProvider; + this.setRoot(root); + this.preSubdivide(); + } + setRoot(root) { + if (typeof root === 'number') { + if (!MapView.mapModes.has(root)) { + throw new Error('Map mode ' + root + ' does is not registered.'); + } + const rootConstructor = MapView.mapModes.get(root); + root = new rootConstructor(null, this); + } + if (this.root !== null) { + this.remove(this.root); + this.root = null; + } + this.root = root; + if (this.root !== null) { + this.geometry = this.root.constructor.baseGeometry; + this.scale.copy(this.root.constructor.baseScale); + this.root.mapView = this; + this.add(this.root); + this.root.initialize(); + } + } + preSubdivide() { + var _a, _b; + function subdivide(node, depth) { + if (depth <= 0) { + return; + } + node.subdivide(); + for (let i = 0; i < node.children.length; i++) { + if (node.children[i] instanceof MapNode) { + const child = node.children[i]; + subdivide(child, depth - 1); + } + } + } + const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + if (minZoom > 0) { + subdivide(this.root, minZoom); + } + } + setProvider(provider) { + if (provider !== this.provider) { + this.provider = provider; + this.clear(); + } + } + setHeightProvider(heightProvider) { + if (heightProvider !== this.heightProvider) { + this.heightProvider = heightProvider; + this.clear(); + } + } + clear() { + this.traverse(function (children) { + if (children.childrenCache) { + children.childrenCache = null; + } + if (children.initialize) { + children.initialize(); + } + }); + return this; + } + minZoom() { + var _a, _b; + return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + } + maxZoom() { + var _a, _b; + return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); + } + getMetaData() { + this.provider.getMetaData(); + } + raycast(raycaster, intersects) { + return false; + } + } + MapView.PLANAR = 200; + MapView.SPHERICAL = 201; + MapView.HEIGHT = 202; + MapView.HEIGHT_SHADER = 203; + MapView.MARTINI = 204; + MapView.mapModes = new Map([ + [MapView.PLANAR, MapPlaneNode], + [MapView.SPHERICAL, MapSphereNode], + [MapView.HEIGHT, MapHeightNode], + [MapView.HEIGHT_SHADER, MapHeightNodeShader], + [MapView.MARTINI, MapMartiniHeightNode] ]); - const pov$1 = new three.Vector3(); - const position$1 = new three.Vector3(); - class LODRadial { - constructor(subdivideDistance = 50, simplifyDistance = 300) { - this.subdivideDistance = subdivideDistance; - this.simplifyDistance = simplifyDistance; - } - updateLOD(view, camera, renderer, scene) { - camera.getWorldPosition(pov$1); - view.children[0].traverse((node) => { - node.getWorldPosition(position$1); - let distance = pov$1.distanceTo(position$1); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - if (distance < this.subdivideDistance) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } + const pov$1 = new three.Vector3(); + const position$1 = new three.Vector3(); + class LODRadial { + constructor(subdivideDistance = 50, simplifyDistance = 300) { + this.subdivideDistance = subdivideDistance; + this.simplifyDistance = simplifyDistance; + } + updateLOD(view, camera, renderer, scene) { + camera.getWorldPosition(pov$1); + view.children[0].traverse((node) => { + node.getWorldPosition(position$1); + let distance = pov$1.distanceTo(position$1); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + if (distance < this.subdivideDistance) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } - const projection = new three.Matrix4(); - const pov = new three.Vector3(); - const frustum = new three.Frustum(); - const position = new three.Vector3(); - class LODFrustum extends LODRadial { - constructor(subdivideDistance = 120, simplifyDistance = 400) { - super(subdivideDistance, simplifyDistance); - this.testCenter = true; - this.pointOnly = false; - } - updateLOD(view, camera, renderer, scene) { - projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromProjectionMatrix(projection); - camera.getWorldPosition(pov); - view.children[0].traverse((node) => { - node.getWorldPosition(position); - let distance = pov.distanceTo(position); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); - if (distance < this.subdivideDistance && inFrustum) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } + const projection = new three.Matrix4(); + const pov = new three.Vector3(); + const frustum = new three.Frustum(); + const position = new three.Vector3(); + class LODFrustum extends LODRadial { + constructor(subdivideDistance = 120, simplifyDistance = 400) { + super(subdivideDistance, simplifyDistance); + this.testCenter = true; + this.pointOnly = false; + } + updateLOD(view, camera, renderer, scene) { + projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projection); + camera.getWorldPosition(pov); + view.children[0].traverse((node) => { + node.getWorldPosition(position); + let distance = pov.distanceTo(position); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); + if (distance < this.subdivideDistance && inFrustum) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } - class XHRUtils { - static get(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static getRaw(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static request(url, type, header, body, onLoad, onError, onProgress) { - function parseResponse(response) { - try { - return JSON.parse(response); - } - catch (e) { - return response; - } - } - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open(type, url, true); - if (header !== null && header !== undefined) { - for (const i in header) { - xhr.setRequestHeader(i, header[i]); - } - } - if (onLoad !== undefined) { - xhr.onload = function (event) { - onLoad(parseResponse(xhr.response), xhr); - }; - } - if (onError !== undefined) { - xhr.onerror = onError; - } - if (onProgress !== undefined) { - xhr.onprogress = onProgress; - } - xhr.send(body !== undefined ? body : null); - return xhr; - } + class XHRUtils { + static get(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static getRaw(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static request(url, type, header, body, onLoad, onError, onProgress) { + function parseResponse(response) { + try { + return JSON.parse(response); + } + catch (e) { + return response; + } + } + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open(type, url, true); + if (header !== null && header !== undefined) { + for (const i in header) { + xhr.setRequestHeader(i, header[i]); + } + } + if (onLoad !== undefined) { + xhr.onload = function (event) { + onLoad(parseResponse(xhr.response), xhr); + }; + } + if (onError !== undefined) { + xhr.onerror = onError; + } + if (onProgress !== undefined) { + xhr.onprogress = onProgress; + } + xhr.send(body !== undefined ? body : null); + return xhr; + } } - class BingMapsProvider extends MapProvider { - constructor(apiKey = '', type = BingMapsProvider.AERIAL) { - super(); - this.maxZoom = 19; - this.minZoom = 1; - this.format = 'jpeg'; - this.mapSize = 512; - this.subdomain = 't1'; - this.meta = null; - this.apiKey = apiKey; - this.type = type; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; - const data = yield XHRUtils.get(address); - this.meta = JSON.parse(data); - }); - } - static quadKey(zoom, x, y) { - let quad = ''; - for (let i = zoom; i > 0; i--) { - const mask = 1 << i - 1; - let cell = 0; - if ((x & mask) !== 0) { - cell++; - } - if ((y & mask) !== 0) { - cell += 2; - } - quad += cell; - } - return quad; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; - }); - } - } - BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; - BingMapsProvider.AERIAL = 'a'; - BingMapsProvider.ROAD = 'r'; - BingMapsProvider.AERIAL_LABELS = 'h'; - BingMapsProvider.OBLIQUE = 'o'; + class BingMapsProvider extends MapProvider { + constructor(apiKey = '', type = BingMapsProvider.AERIAL) { + super(); + this.maxZoom = 19; + this.minZoom = 1; + this.format = 'jpeg'; + this.mapSize = 512; + this.subdomain = 't1'; + this.meta = null; + this.apiKey = apiKey; + this.type = type; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; + const data = yield XHRUtils.get(address); + this.meta = JSON.parse(data); + }); + } + static quadKey(zoom, x, y) { + let quad = ''; + for (let i = zoom; i > 0; i--) { + const mask = 1 << i - 1; + let cell = 0; + if ((x & mask) !== 0) { + cell++; + } + if ((y & mask) !== 0) { + cell += 2; + } + quad += cell; + } + return quad; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; + }); + } + } + BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; + BingMapsProvider.AERIAL = 'a'; + BingMapsProvider.ROAD = 'r'; + BingMapsProvider.AERIAL_LABELS = 'h'; + BingMapsProvider.OBLIQUE = 'o'; BingMapsProvider.OBLIQUE_LABELS = 'b'; - class GoogleMapsProvider extends MapProvider { - constructor(apiToken) { - super(); - this.sessionToken = null; - this.orientation = 0; - this.format = 'png'; - this.mapType = 'roadmap'; - this.overlay = false; - this.apiToken = apiToken !== undefined ? apiToken : ''; - this.createSession(); - } - createSession() { - const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; - const data = JSON.stringify({ - mapType: this.mapType, - language: 'en-EN', - region: 'en', - layerTypes: ['layerRoadmap', 'layerStreetview'], - overlay: this.overlay, - scale: 'scaleFactor1x' - }); - XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { - this.sessionToken = response.session; - }, function (xhr) { - throw new Error('Unable to create a google maps session.'); - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; - }); - } + class GoogleMapsProvider extends MapProvider { + constructor(apiToken) { + super(); + this.sessionToken = null; + this.orientation = 0; + this.format = 'png'; + this.mapType = 'roadmap'; + this.overlay = false; + this.apiToken = apiToken !== undefined ? apiToken : ''; + this.createSession(); + } + createSession() { + const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; + const data = JSON.stringify({ + mapType: this.mapType, + language: 'en-EN', + region: 'en', + layerTypes: ['layerRoadmap', 'layerStreetview'], + overlay: this.overlay, + scale: 'scaleFactor1x' + }); + XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + this.sessionToken = response.session; + }, function (xhr) { + throw new Error('Unable to create a google maps session.'); + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + }); + } } - class HereMapsProvider extends MapProvider { - constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { - super(); - this.appId = appId; - this.appCode = appCode; - this.style = style; - this.scheme = scheme; - this.format = format; - this.size = size; - this.version = 'newest'; - this.server = 1; - } - nextServer() { - this.server = this.server % 4 === 0 ? 1 : this.server + 1; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } - fetchTile(zoom, x, y) { - this.nextServer(); - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + - this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + - this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; - }); - } - } + class HereMapsProvider extends MapProvider { + constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { + super(); + this.appId = appId; + this.appCode = appCode; + this.style = style; + this.scheme = scheme; + this.format = format; + this.size = size; + this.version = 'newest'; + this.server = 1; + } + nextServer() { + this.server = this.server % 4 === 0 ? 1 : this.server + 1; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } + fetchTile(zoom, x, y) { + this.nextServer(); + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + + this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + + this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; + }); + } + } HereMapsProvider.PATH = '/maptile/2.1/'; - class MapBoxProvider extends MapProvider { - constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { - super(); - this.apiToken = apiToken; - this.format = format; - this.useHDPI = useHDPI; - this.mode = mode; - this.mapId = id; - this.style = id; - this.version = version; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - if (this.mode === MapBoxProvider.STYLE) { - image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; - } - else { - image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; - } - }); - } - } - MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; - MapBoxProvider.STYLE = 100; + class MapBoxProvider extends MapProvider { + constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { + super(); + this.apiToken = apiToken; + this.format = format; + this.useHDPI = useHDPI; + this.mode = mode; + this.mapId = id; + this.style = id; + this.version = version; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + if (this.mode === MapBoxProvider.STYLE) { + image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; + } + else { + image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; + } + }); + } + } + MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; + MapBoxProvider.STYLE = 100; MapBoxProvider.MAP_ID = 101; - class MapTilerProvider extends MapProvider { - constructor(apiKey, category, style, format) { - super(); - this.apiKey = apiKey !== undefined ? apiKey : ''; - this.format = format !== undefined ? format : 'png'; - this.category = category !== undefined ? category : 'maps'; - this.style = style !== undefined ? style : 'satellite'; - this.resolution = 512; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; - }); - } + class MapTilerProvider extends MapProvider { + constructor(apiKey, category, style, format) { + super(); + this.apiKey = apiKey !== undefined ? apiKey : ''; + this.format = format !== undefined ? format : 'png'; + this.category = category !== undefined ? category : 'maps'; + this.style = style !== undefined ? style : 'satellite'; + this.resolution = 512; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; + }); + } } - class OpenMapTilesProvider extends MapProvider { - constructor(address, format = 'png', theme = 'klokantech-basic') { - super(); - this.address = address; - this.format = format; - this.theme = theme; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = this.address + 'styles/' + this.theme + '.json'; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.format = meta.format; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } + class OpenMapTilesProvider extends MapProvider { + constructor(address, format = 'png', theme = 'klokantech-basic') { + super(); + this.address = address; + this.format = format; + this.theme = theme; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = this.address + 'styles/' + this.theme + '.json'; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.format = meta.format; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } - class DebugProvider extends MapProvider { - constructor() { - super(...arguments); - this.resolution = 256; - } - fetchTile(zoom, x, y) { - const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); - const context = canvas.getContext('2d'); - const green = new three.Color(0x00ff00); - const red = new three.Color(0xff0000); - const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); - context.fillStyle = color.getStyle(); - context.fillRect(0, 0, this.resolution, this.resolution); - context.fillStyle = '#000000'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; - context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); - context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); - return Promise.resolve(canvas); - } + class DebugProvider extends MapProvider { + constructor() { + super(...arguments); + this.resolution = 256; + } + fetchTile(zoom, x, y) { + const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); + const context = canvas.getContext('2d'); + const green = new three.Color(0x00ff00); + const red = new three.Color(0xff0000); + const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); + context.fillStyle = color.getStyle(); + context.fillRect(0, 0, this.resolution, this.resolution); + context.fillStyle = '#000000'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; + context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); + context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); + return Promise.resolve(canvas); + } } - class HeightDebugProvider extends MapProvider { - constructor(provider) { - super(); - this.fromColor = new three.Color(0xff0000); - this.toColor = new three.Color(0x00ff00); - this.provider = provider; - } - fetchTile(zoom, x, y) { - return __awaiter(this, void 0, void 0, function* () { - const image = yield this.provider.fetchTile(zoom, x, y); - const resolution = 256; - const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); - const imageData = context.getImageData(0, 0, resolution, resolution); - const data = imageData.data; - for (let i = 0; i < data.length; i += 4) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - const max = 1667721.6; - const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); - data[i] = color.r * 255; - data[i + 1] = color.g * 255; - data[i + 2] = color.b * 255; - } - context.putImageData(imageData, 0, 0); - return canvas; - }); - } + class HeightDebugProvider extends MapProvider { + constructor(provider) { + super(); + this.fromColor = new three.Color(0xff0000); + this.toColor = new three.Color(0x00ff00); + this.provider = provider; + } + fetchTile(zoom, x, y) { + return __awaiter(this, void 0, void 0, function* () { + const image = yield this.provider.fetchTile(zoom, x, y); + const resolution = 256; + const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); + const imageData = context.getImageData(0, 0, resolution, resolution); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + const max = 1667721.6; + const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); + data[i] = color.r * 255; + data[i + 1] = color.g * 255; + data[i + 2] = color.b * 255; + } + context.putImageData(imageData, 0, 0); + return canvas; + }); + } } - class GeolocationUtils { - static get() { - return new Promise(function (resolve, reject) { - navigator.geolocation.getCurrentPosition(function (result) { - resolve(result); - }, reject); - }); - } + class GeolocationUtils { + static get() { + return new Promise(function (resolve, reject) { + navigator.geolocation.getCurrentPosition(function (result) { + resolve(result); + }, reject); + }); + } } - class CancelablePromise { - constructor(executor) { - this.fulfilled = false; - this.rejected = false; - this.called = false; - const resolve = (v) => { - this.fulfilled = true; - this.value = v; - if (typeof this.onResolve === 'function') { - this.onResolve(this.value); - this.called = true; - } - }; - const reject = (reason) => { - this.rejected = true; - this.value = reason; - if (typeof this.onReject === 'function') { - this.onReject(this.value); - this.called = true; - } - }; - try { - executor(resolve, reject); - } - catch (error) { - reject(error); - } - } - cancel() { - return false; - } - then(callback) { - this.onResolve = callback; - if (this.fulfilled && !this.called) { - this.called = true; - this.onResolve(this.value); - } - return this; - } - catch(callback) { - this.onReject = callback; - if (this.rejected && !this.called) { - this.called = true; - this.onReject(this.value); - } - return this; - } - finally(callback) { - return this; - } - static resolve(val) { - return new CancelablePromise(function executor(resolve, _reject) { - resolve(val); - }); - } - static reject(reason) { - return new CancelablePromise(function executor(resolve, reject) { - reject(reason); - }); - } - static all(promises) { - const fulfilledPromises = []; - const result = []; - function executor(resolve, reject) { - promises.forEach((promise, index) => { - return promise - .then((val) => { - fulfilledPromises.push(true); - result[index] = val; - if (fulfilledPromises.length === promises.length) { - return resolve(result); - } - }) - .catch((error) => { return reject(error); }); - }); - } - return new CancelablePromise(executor); - } + class CancelablePromise { + constructor(executor) { + this.fulfilled = false; + this.rejected = false; + this.called = false; + const resolve = (v) => { + this.fulfilled = true; + this.value = v; + if (typeof this.onResolve === 'function') { + this.onResolve(this.value); + this.called = true; + } + }; + const reject = (reason) => { + this.rejected = true; + this.value = reason; + if (typeof this.onReject === 'function') { + this.onReject(this.value); + this.called = true; + } + }; + try { + executor(resolve, reject); + } + catch (error) { + reject(error); + } + } + cancel() { + return false; + } + then(callback) { + this.onResolve = callback; + if (this.fulfilled && !this.called) { + this.called = true; + this.onResolve(this.value); + } + return this; + } + catch(callback) { + this.onReject = callback; + if (this.rejected && !this.called) { + this.called = true; + this.onReject(this.value); + } + return this; + } + finally(callback) { + return this; + } + static resolve(val) { + return new CancelablePromise(function executor(resolve, _reject) { + resolve(val); + }); + } + static reject(reason) { + return new CancelablePromise(function executor(resolve, reject) { + reject(reason); + }); + } + static all(promises) { + const fulfilledPromises = []; + const result = []; + function executor(resolve, reject) { + promises.forEach((promise, index) => { + return promise + .then((val) => { + fulfilledPromises.push(true); + result[index] = val; + if (fulfilledPromises.length === promises.length) { + return resolve(result); + } + }) + .catch((error) => { return reject(error); }); + }); + } + return new CancelablePromise(executor); + } } exports.BingMapsProvider = BingMapsProvider; diff --git a/build/geo-three.module.js b/build/geo-three.module.js index 68cbb61..6e9b5f2 100644 --- a/build/geo-three.module.js +++ b/build/geo-three.module.js @@ -1,4 +1,4 @@ -import { Texture, RGBAFormat, LinearFilter, Mesh, BufferGeometry, Float32BufferAttribute, Vector2, Vector3, MeshBasicMaterial, MeshPhongMaterial, Matrix4, Quaternion, NearestFilter, Raycaster, DoubleSide, Uint32BufferAttribute, Frustum, Color } from 'three'; +import { Texture, RGBAFormat, LinearFilter, Mesh, REVISION, BufferGeometry, Float32BufferAttribute, Vector2, Vector3, MeshBasicMaterial, MeshPhongMaterial, Vector4, ShaderMaterial, Matrix4, Quaternion, TextureLoader, NearestFilter, Raycaster, DoubleSide, Uint32BufferAttribute, Frustum, Color } from 'three'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. @@ -25,740 +25,825 @@ function __awaiter(thisArg, _arguments, P, generator) { }); } -class MapProvider { - constructor() { - this.name = ''; - this.minZoom = 0; - this.maxZoom = 20; - this.bounds = []; - this.center = []; - } - fetchTile(zoom, x, y) { - return null; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } +class MapProvider { + constructor() { + this.name = ''; + this.minZoom = 0; + this.maxZoom = 20; + this.bounds = []; + this.center = []; + } + fetchTile(zoom, x, y) { + return null; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } } -class OpenStreetMapsProvider extends MapProvider { - constructor(address = 'https://a.tile.openstreetmap.org/') { - super(); - this.address = address; - this.format = 'png'; - this.maxZoom = 19; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } +class OpenStreetMapsProvider extends MapProvider { + constructor(address = 'https://a.tile.openstreetmap.org/') { + super(); + this.address = address; + this.format = 'png'; + this.maxZoom = 19; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } -class CanvasUtils { - static createOffscreenCanvas(width, height) { - if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(width, height); - } - else { - let canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - } +class CanvasUtils { + static createOffscreenCanvas(width, height) { + if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(width, height); + } + else { + let canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + } + } } -class TextureUtils { - static createFillTexture(color = '#000000', width = 1, height = 1) { - const canvas = CanvasUtils.createOffscreenCanvas(width, height); - const context = canvas.getContext('2d'); - context.fillStyle = color; - context.fillRect(0, 0, width, height); - const texture = new Texture(canvas); - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; - texture.needsUpdate = true; - return texture; - } +class TextureUtils { + static createFillTexture(color = '#000000', width = 1, height = 1) { + const canvas = CanvasUtils.createOffscreenCanvas(width, height); + const context = canvas.getContext('2d'); + context.fillStyle = color; + context.fillRect(0, 0, width, height); + const texture = new Texture(canvas); + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + return texture; + } } -class QuadTreePosition { -} -QuadTreePosition.root = -1; -QuadTreePosition.topLeft = 0; -QuadTreePosition.topRight = 1; -QuadTreePosition.bottomLeft = 2; -QuadTreePosition.bottomRight = 3; -class MapNode extends Mesh { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { - super(geometry, material); - this.mapView = null; - this.parentNode = null; - this.subdivided = false; - this.disposed = false; - this.nodesLoaded = 0; - this.childrenCache = null; - this.isMesh = true; - this.mapView = mapView; - this.parentNode = parentNode; - this.disposed = false; - this.location = location; - this.level = level; - this.x = x; - this.y = y; - this.initialize(); - } - initialize() { - return __awaiter(this, void 0, void 0, function* () { }); - } - createChildNodes() { } - subdivide() { - const maxZoom = this.mapView.maxZoom(); - if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { - return; - } - if (this.mapView.cacheTiles && this.childrenCache !== null) { - this.isMesh = false; - this.children = this.childrenCache; - this.nodesLoaded = this.childrenCache.length; - } - else { - this.createChildNodes(); - } - this.subdivided = true; - } - simplify() { - const minZoom = this.mapView.minZoom(); - if (this.level - 1 < minZoom) { - return; - } - if (this.mapView.cacheTiles) { - this.childrenCache = this.children; - } - else { - for (let i = 0; i < this.children.length; i++) { - this.children[i].dispose(); - } - } - this.subdivided = false; - this.isMesh = true; - this.children = []; - this.nodesLoaded = 0; - } - loadData() { - return __awaiter(this, void 0, void 0, function* () { - if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapNode.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.warn('Geo-Three: Failed to load node tile data.', this); - this.material.map = MapNode.defaultTexture; - } - this.material.needsUpdate = true; - }); - } - nodeReady() { - if (this.disposed) { - console.warn('Geo-Three: nodeReady() called for disposed node.', this); - this.dispose(); - return; - } - if (this.parentNode !== null) { - this.parentNode.nodesLoaded++; - if (this.parentNode.nodesLoaded === MapNode.childrens) { - if (this.parentNode.subdivided === true) { - this.parentNode.isMesh = false; - } - for (let i = 0; i < this.parentNode.children.length; i++) { - this.parentNode.children[i].visible = true; - } - } - if (this.parentNode.nodesLoaded > MapNode.childrens) { - console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); - } - } - else { - this.visible = true; - } - } - dispose() { - this.disposed = true; - const self = this; - try { - const material = self.material; - material.dispose(); - if (material.map && material.map !== MapNode.defaultTexture) { - material.map.dispose(); - } - } - catch (e) { } - try { - self.geometry.dispose(); - } - catch (e) { } - } -} -MapNode.defaultTexture = TextureUtils.createFillTexture(); -MapNode.baseGeometry = null; -MapNode.baseScale = null; +class QuadTreePosition { +} +QuadTreePosition.root = -1; +QuadTreePosition.topLeft = 0; +QuadTreePosition.topRight = 1; +QuadTreePosition.bottomLeft = 2; +QuadTreePosition.bottomRight = 3; +class MapNode extends Mesh { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { + super(geometry, material); + this.mapView = null; + this.parentNode = null; + this.subdivided = false; + this.disposed = false; + this.nodesLoaded = 0; + this.childrenCache = null; + this.isMesh = true; + this.mapView = mapView; + this.parentNode = parentNode; + this.disposed = false; + this.location = location; + this.level = level; + this.x = x; + this.y = y; + this.initialize(); + } + initialize() { + return __awaiter(this, void 0, void 0, function* () { }); + } + createChildNodes() { } + subdivide() { + const maxZoom = this.mapView.maxZoom(); + if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { + return; + } + if (this.mapView.cacheTiles && this.childrenCache !== null) { + this.isMesh = false; + this.children = this.childrenCache; + this.nodesLoaded = this.childrenCache.length; + } + else { + this.createChildNodes(); + } + this.subdivided = true; + } + simplify() { + const minZoom = this.mapView.minZoom(); + if (this.level - 1 < minZoom) { + return; + } + if (this.mapView.cacheTiles) { + this.childrenCache = this.children; + } + else { + for (let i = 0; i < this.children.length; i++) { + this.children[i].dispose(); + } + } + this.subdivided = false; + this.isMesh = true; + this.children = []; + this.nodesLoaded = 0; + } + loadData() { + return __awaiter(this, void 0, void 0, function* () { + if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapNode.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); + yield this.applyTexture(image); + } + catch (e) { + if (this.disposed) { + return; + } + console.warn('Geo-Three: Failed to load node tile data.', this); + this.material.map = MapNode.defaultTexture; + } + this.material.needsUpdate = true; + }); + } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new Texture(image); + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } + nodeReady() { + if (this.disposed) { + console.warn('Geo-Three: nodeReady() called for disposed node.', this); + this.dispose(); + return; + } + if (this.parentNode !== null) { + this.parentNode.nodesLoaded++; + if (this.parentNode.nodesLoaded === MapNode.childrens) { + if (this.parentNode.subdivided === true) { + this.parentNode.isMesh = false; + } + for (let i = 0; i < this.parentNode.children.length; i++) { + this.parentNode.children[i].visible = true; + } + } + if (this.parentNode.nodesLoaded > MapNode.childrens) { + console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); + } + } + else { + this.visible = true; + } + } + dispose() { + this.disposed = true; + const self = this; + try { + const material = self.material; + material.dispose(); + if (material.map && material.map !== MapNode.defaultTexture) { + material.map.dispose(); + } + } + catch (e) { } + try { + self.geometry.dispose(); + } + catch (e) { } + } +} +MapNode.defaultTexture = TextureUtils.createFillTexture(); +MapNode.baseGeometry = null; +MapNode.baseScale = null; MapNode.childrens = 4; -class MapNodeGeometry extends BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - } - static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - vertices.push(x, 0, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1 - iz / heightSegments); - } - } - for (let iz = 0; iz < heightSegments; iz++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix + gridX * iz; - const b = ix + gridX * (iz + 1); - const c = ix + 1 + gridX * (iz + 1); - const d = ix + 1 + gridX * iz; - indices.push(a, b, d, b, c, d); - } - } - } - static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - let start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = -heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1); - } - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix; - const d = ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(d, b, a, d, c, b); - } - start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = heightSegments * segmentHeight - heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 0); - } - let offset = gridX * gridZ - widthSegments - 1; - for (let ix = 0; ix < widthSegments; ix++) { - const a = offset + ix; - const d = offset + ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = -widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ; - const d = (iz + 1) * gridZ; - const b = iz + start; - const c = iz + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = widthSegments * segmentWidth - widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(1.0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ + heightSegments; - const d = (iz + 1) * gridZ + heightSegments; - const b = iz + start; - const c = iz + start + 1; - indices.push(d, b, a, d, c, b); - } - } +class MapNodeGeometry extends BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + } + static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + vertices.push(x, 0, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1 - iz / heightSegments); + } + } + for (let iz = 0; iz < heightSegments; iz++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix + gridX * iz; + const b = ix + gridX * (iz + 1); + const c = ix + 1 + gridX * (iz + 1); + const d = ix + 1 + gridX * iz; + indices.push(a, b, d, b, c, d); + } + } + } + static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + let start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = -heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1); + } + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix; + const d = ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(d, b, a, d, c, b); + } + start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = heightSegments * segmentHeight - heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 0); + } + let offset = gridX * gridZ - widthSegments - 1; + for (let ix = 0; ix < widthSegments; ix++) { + const a = offset + ix; + const d = offset + ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = -widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ; + const d = (iz + 1) * gridZ; + const b = iz + start; + const c = iz + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = widthSegments * segmentWidth - widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(1.0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ + heightSegments; + const d = (iz + 1) * gridZ + heightSegments; + const b = iz + start; + const c = iz + start + 1; + indices.push(d, b, a, d, c, b); + } + } } -class Geolocation { - constructor(latitude, longitude) { - this.latitude = latitude; - this.longitude = longitude; - } +class Geolocation { + constructor(latitude, longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } -class UnitsUtils { - static datumsToSpherical(latitude, longitude) { - const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; - let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); - y = y * UnitsUtils.EARTH_ORIGIN / 180.0; - return new Vector2(x, y); - } - static sphericalToDatums(x, y) { - const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; - let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; - latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); - return new Geolocation(latitude, longitude); - } - static quadtreeToDatums(zoom, x, y) { - const n = Math.pow(2.0, zoom); - const longitude = x / n * 360.0 - 180.0; - const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); - const latitude = 180.0 * (latitudeRad / Math.PI); - return new Geolocation(latitude, longitude); - } - static vectorToDatums(dir) { - const radToDeg = 180 / Math.PI; - const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; - const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; - return new Geolocation(latitude, longitude); - } - static datumsToVector(latitude, longitude) { - const degToRad = Math.PI / 180; - const rotX = longitude * degToRad; - const rotY = latitude * degToRad; - var cos = Math.cos(rotY); - return new Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); - } - static mapboxAltitude(color) { - return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; - } -} -UnitsUtils.EARTH_RADIUS = 6371008; -UnitsUtils.EARTH_RADIUS_A = 6378137.0; -UnitsUtils.EARTH_RADIUS_B = 6356752.314245; -UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; +class UnitsUtils { + static datumsToSpherical(latitude, longitude) { + const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; + let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); + y = y * UnitsUtils.EARTH_ORIGIN / 180.0; + return new Vector2(x, y); + } + static sphericalToDatums(x, y) { + const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; + let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; + latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); + return new Geolocation(latitude, longitude); + } + static quadtreeToDatums(zoom, x, y) { + const n = Math.pow(2.0, zoom); + const longitude = x / n * 360.0 - 180.0; + const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); + const latitude = 180.0 * (latitudeRad / Math.PI); + return new Geolocation(latitude, longitude); + } + static vectorToDatums(dir) { + const radToDeg = 180 / Math.PI; + const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; + const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; + return new Geolocation(latitude, longitude); + } + static datumsToVector(latitude, longitude) { + const degToRad = Math.PI / 180; + const rotX = longitude * degToRad; + const rotY = latitude * degToRad; + var cos = Math.cos(rotY); + return new Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); + } + static mapboxAltitude(color) { + return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; + } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } +} +UnitsUtils.EARTH_RADIUS = 6371008; +UnitsUtils.EARTH_RADIUS_A = 6378137.0; +UnitsUtils.EARTH_RADIUS_B = 6356752.314245; +UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; +UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; -class MapPlaneNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new MeshBasicMaterial({ wireframe: false })); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); -MapPlaneNode.baseGeometry = MapPlaneNode.geometry; +class MapPlaneNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new MeshBasicMaterial({ wireframe: false })); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); +MapPlaneNode.baseGeometry = MapPlaneNode.geometry; MapPlaneNode.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1.0, UnitsUtils.EARTH_PERIMETER); -class MapNodeHeightGeometry extends BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - const data = imageData.data; - for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - vertices[j + 1] = value; - } - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - if (calculateNormals) { - this.computeNormals(widthSegments, heightSegments); - } - } - computeNormals(widthSegments, heightSegments) { - const positionAttribute = this.getAttribute('position'); - if (positionAttribute !== undefined) { - let normalAttribute = this.getAttribute('normal'); - const normalLength = heightSegments * widthSegments; - for (let i = 0; i < normalLength; i++) { - normalAttribute.setXYZ(i, 0, 0, 0); - } - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); - const indexLength = heightSegments * widthSegments * 6; - for (let i = 0; i < indexLength; i += 3) { - const vA = this.index.getX(i + 0); - const vB = this.index.getX(i + 1); - const vC = this.index.getX(i + 2); - pA.fromBufferAttribute(positionAttribute, vA); - pB.fromBufferAttribute(positionAttribute, vB); - pC.fromBufferAttribute(positionAttribute, vC); - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - nA.fromBufferAttribute(normalAttribute, vA); - nB.fromBufferAttribute(normalAttribute, vB); - nC.fromBufferAttribute(normalAttribute, vC); - nA.add(cb); - nB.add(cb); - nC.add(cb); - normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); - normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); - normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); - } - this.normalizeNormals(); - normalAttribute.needsUpdate = true; - } - } +class MapNodeHeightGeometry extends BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + const data = imageData.data; + for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + vertices[j + 1] = value; + } + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + if (calculateNormals) { + this.computeNormals(widthSegments, heightSegments); + } + } + computeNormals(widthSegments, heightSegments) { + const positionAttribute = this.getAttribute('position'); + if (positionAttribute !== undefined) { + let normalAttribute = this.getAttribute('normal'); + const normalLength = heightSegments * widthSegments; + for (let i = 0; i < normalLength; i++) { + normalAttribute.setXYZ(i, 0, 0, 0); + } + const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); + const cb = new Vector3(), ab = new Vector3(); + const indexLength = heightSegments * widthSegments * 6; + for (let i = 0; i < indexLength; i += 3) { + const vA = this.index.getX(i + 0); + const vB = this.index.getX(i + 1); + const vC = this.index.getX(i + 2); + pA.fromBufferAttribute(positionAttribute, vA); + pB.fromBufferAttribute(positionAttribute, vB); + pC.fromBufferAttribute(positionAttribute, vC); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + nA.fromBufferAttribute(normalAttribute, vA); + nB.fromBufferAttribute(normalAttribute, vB); + nC.fromBufferAttribute(normalAttribute, vC); + nA.add(cb); + nB.add(cb); + nC.add(cb); + normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); + normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); + normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); + } + this.normalizeNormals(); + normalAttribute.needsUpdate = true; + } + } } -class MapHeightNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { - super(parentNode, mapView, location, level, x, y, geometry, material); - this.heightLoaded = false; - this.textureLoaded = false; - this.geometrySize = 16; - this.geometryNormals = false; - this.isMesh = true; - this.visible = false; - this.matrixAutoUpdate = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - yield this.loadHeightGeometry(); - this.nodeReady(); - }); - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.geometry = MapPlaneNode.baseGeometry; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); - const context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); - const imageData = context.getImageData(0, 0, canvas.width, canvas.height); - this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); - } - catch (e) { - if (this.disposed) { - return; - } - this.geometry = MapPlaneNode.baseGeometry; - } - this.heightLoaded = true; - }); - } - createChildNodes() { - const level = this.level + 1; - const Constructor = Object.getPrototypeOf(this).constructor; - const x = this.x * 2; - const y = this.y * 2; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapHeightNode.tileSize = 256; -MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); -MapHeightNode.baseGeometry = MapPlaneNode.geometry; +class MapHeightNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { + super(parentNode, mapView, location, level, x, y, geometry, material); + this.heightLoaded = false; + this.textureLoaded = false; + this.geometrySize = 16; + this.geometryNormals = false; + this.isMesh = true; + this.visible = false; + this.matrixAutoUpdate = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + yield this.loadHeightGeometry(); + this.nodeReady(); + }); + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.geometry = MapPlaneNode.baseGeometry; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); + } + catch (e) { + if (this.disposed) { + return; + } + this.geometry = MapPlaneNode.baseGeometry; + } + this.heightLoaded = true; + }); + } + createChildNodes() { + const level = this.level + 1; + const Constructor = Object.getPrototypeOf(this).constructor; + const x = this.x * 2; + const y = this.y * 2; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapHeightNode.tileSize = 256; +MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); +MapHeightNode.baseGeometry = MapPlaneNode.geometry; MapHeightNode.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); -class MapSphereNodeGeometry extends BufferGeometry { - constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { - super(); - const thetaEnd = thetaStart + thetaLength; - let index = 0; - const grid = []; - const vertex = new Vector3(); - const normal = new Vector3(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - for (let iy = 0; iy <= heightSegments; iy++) { - const verticesRow = []; - const v = iy / heightSegments; - for (let ix = 0; ix <= widthSegments; ix++) { - const u = ix / widthSegments; - vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertex.y = radius * Math.cos(thetaStart + v * thetaLength); - vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertices.push(vertex.x, vertex.y, vertex.z); - normal.set(vertex.x, vertex.y, vertex.z).normalize(); - normals.push(normal.x, normal.y, normal.z); - uvs.push(u, 1 - v); - verticesRow.push(index++); - } - grid.push(verticesRow); - } - for (let iy = 0; iy < heightSegments; iy++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = grid[iy][ix + 1]; - const b = grid[iy][ix]; - const c = grid[iy + 1][ix]; - const d = grid[iy + 1][ix + 1]; - if (iy !== 0 || thetaStart > 0) { - indices.push(a, b, d); - } - if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { - indices.push(b, c, d); - } - } - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - } +class MapSphereNodeGeometry extends BufferGeometry { + constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { + super(); + const thetaEnd = thetaStart + thetaLength; + let index = 0; + const grid = []; + const vertex = new Vector3(); + const normal = new Vector3(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + for (let iy = 0; iy <= heightSegments; iy++) { + const verticesRow = []; + const v = iy / heightSegments; + for (let ix = 0; ix <= widthSegments; ix++) { + const u = ix / widthSegments; + vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertex.y = radius * Math.cos(thetaStart + v * thetaLength); + vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertices.push(vertex.x, vertex.y, vertex.z); + normal.set(vertex.x, vertex.y, vertex.z).normalize(); + normals.push(normal.x, normal.y, normal.z); + uvs.push(u, 1 - v); + verticesRow.push(index++); + } + grid.push(verticesRow); + } + for (let iy = 0; iy < heightSegments; iy++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = grid[iy][ix + 1]; + const b = grid[iy][ix]; + const c = grid[iy + 1][ix]; + const d = grid[iy + 1][ix + 1]; + if (iy !== 0 || thetaStart > 0) { + indices.push(a, b, d); + } + if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { + indices.push(b, c, d); + } + } + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + } } -class MapSphereNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); - this.applyScaleNode(); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - static createGeometry(zoom, x, y) { - const range = Math.pow(2, zoom); - const max = 40; - const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; - return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); - } - applyScaleNode() { - this.geometry.computeBoundingBox(); - const box = this.geometry.boundingBox.clone(); - const center = box.getCenter(new Vector3()); - const matrix = new Matrix4(); - matrix.compose(new Vector3(-center.x, -center.y, -center.z), new Quaternion(), new Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); - this.geometry.applyMatrix4(matrix); - this.position.copy(center); - this.updateMatrix(); - this.updateMatrixWorld(); - } - updateMatrix() { - this.matrix.setPosition(this.position); - this.matrixWorldNeedsUpdate = true; - } - updateMatrixWorld(force = false) { - if (this.matrixWorldNeedsUpdate || force) { - this.matrixWorld.copy(this.matrix); - this.matrixWorldNeedsUpdate = false; - } - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - this.add(node); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); -MapSphereNode.baseScale = new Vector3(1, 1, 1); +class MapSphereNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new Vector4(...bounds); + const material = new ShaderMaterial({ + uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + this.applyScaleNode(); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + static createGeometry(zoom, x, y) { + const range = Math.pow(2, zoom); + const max = 40; + const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); + return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); + } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } + applyScaleNode() { + this.geometry.computeBoundingBox(); + const box = this.geometry.boundingBox.clone(); + const center = box.getCenter(new Vector3()); + const matrix = new Matrix4(); + matrix.compose(new Vector3(-center.x, -center.y, -center.z), new Quaternion(), new Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); + this.geometry.applyMatrix4(matrix); + this.position.copy(center); + this.updateMatrix(); + this.updateMatrixWorld(); + } + updateMatrix() { + this.matrix.setPosition(this.position); + this.matrixWorldNeedsUpdate = true; + } + updateMatrixWorld(force = false) { + if (this.matrixWorldNeedsUpdate || force) { + this.matrixWorld.copy(this.matrix); + this.matrixWorldNeedsUpdate = false; + } + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + this.add(node); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); +MapSphereNode.baseScale = new Vector3(1, 1, 1); MapSphereNode.segments = 80; -class MapHeightNodeShader extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - const material = MapHeightNodeShader.prepareMaterial(new MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); - super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); - this.frustumCulled = false; - } - static prepareMaterial(material) { - material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; - material.onBeforeCompile = (shader) => { - for (const i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = +class MapHeightNodeShader extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + const material = MapHeightNodeShader.prepareMaterial(new MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); + super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); + this.frustumCulled = false; + } + static prepareMaterial(material) { + material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; + material.onBeforeCompile = (shader) => { + for (const i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform sampler2D heightMap; - ` + shader.vertexShader; + ` + shader.vertexShader; shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -769,389 +854,393 @@ class MapHeightNodeShader extends MapHeightNode { // Vertex position based on height gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0); - `); - }; - return material; - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapHeightNodeShader.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = NearestFilter; - texture.minFilter = NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.error('Geo-Three: Failed to load node tile height data.', this); - this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; - } - this.material.needsUpdate = true; - this.heightLoaded = true; - }); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - this.geometry = MapPlaneNode.geometry; - super.raycast(raycaster, intersects); - this.geometry = MapHeightNodeShader.geometry; - } - } - dispose() { - super.dispose(); - if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { - this.material.userData.heightMap.value.dispose(); - } - } -} -MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); -MapHeightNodeShader.geometrySize = 256; -MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); -MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; + `); + }; + return material; + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapHeightNodeShader.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.error('Geo-Three: Failed to load node tile height data.', this); + this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; + } + this.material.needsUpdate = true; + this.heightLoaded = true; + }); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + this.geometry = MapPlaneNode.geometry; + super.raycast(raycaster, intersects); + this.geometry = MapHeightNodeShader.geometry; + } + } + dispose() { + super.dispose(); + if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { + this.material.userData.heightMap.value.dispose(); + } + } +} +MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); +MapHeightNodeShader.geometrySize = 256; +MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); +MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; MapHeightNodeShader.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); -class LODRaycast { - constructor() { - this.subdivisionRays = 1; - this.thresholdUp = 0.6; - this.thresholdDown = 0.15; - this.raycaster = new Raycaster(); - this.mouse = new Vector2(); - this.powerDistance = false; - this.scaleDistance = true; - } - updateLOD(view, camera, renderer, scene) { - const intersects = []; - for (let t = 0; t < this.subdivisionRays; t++) { - this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); - this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); - } - for (let i = 0; i < intersects.length; i++) { - const node = intersects[i].object; - let distance = intersects[i].distance; - if (this.powerDistance) { - distance = Math.pow(distance * 2, node.level); - } - if (this.scaleDistance) { - const matrix = node.matrixWorld.elements; - const vector = new Vector3(matrix[0], matrix[1], matrix[2]); - distance = vector.length() / distance; - } - if (distance > this.thresholdUp) { - node.subdivide(); - } - else if (distance < this.thresholdDown && node.parentNode) { - node.parentNode.simplify(); - } - } - } +class LODRaycast { + constructor() { + this.subdivisionRays = 1; + this.thresholdUp = 0.6; + this.thresholdDown = 0.15; + this.raycaster = new Raycaster(); + this.mouse = new Vector2(); + this.powerDistance = false; + this.scaleDistance = true; + } + updateLOD(view, camera, renderer, scene) { + const intersects = []; + for (let t = 0; t < this.subdivisionRays; t++) { + this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); + this.raycaster.setFromCamera(this.mouse, camera); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } + } + for (let i = 0; i < intersects.length; i++) { + const node = intersects[i].object; + let distance = intersects[i].distance; + if (this.powerDistance) { + distance = Math.pow(distance * 2, node.level); + } + if (this.scaleDistance) { + const matrix = node.matrixWorld.elements; + const vector = new Vector3(matrix[0], matrix[1], matrix[2]); + distance = vector.length() / distance; + } + if (distance > this.thresholdUp) { + node.subdivide(); + } + else if (distance < this.thresholdDown && node.parentNode) { + node.parentNode.simplify(); + } + } + } } -class Martini { - constructor(gridSize = 257) { - this.gridSize = gridSize; - const tileSize = gridSize - 1; - if (tileSize & tileSize - 1) { - throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); - } - this.numTriangles = tileSize * tileSize * 2 - 2; - this.numParentTriangles = this.numTriangles - tileSize * tileSize; - this.indices = new Uint32Array(this.gridSize * this.gridSize); - this.coords = new Uint16Array(this.numTriangles * 4); - for (let i = 0; i < this.numTriangles; i++) { - let id = i + 2; - let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; - if (id & 1) { - bx = by = cx = tileSize; - } - else { - ax = ay = cy = tileSize; - } - while ((id >>= 1) > 1) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (id & 1) { - bx = ax; - by = ay; - ax = cx; - ay = cy; - } - else { - ax = bx; - ay = by; - bx = cx; - by = cy; - } - cx = mx; - cy = my; - } - const k = i * 4; - this.coords[k + 0] = ax; - this.coords[k + 1] = ay; - this.coords[k + 2] = bx; - this.coords[k + 3] = by; - } - } - createTile(terrain) { - return new Tile(terrain, this); - } -} -class Tile { - constructor(terrain, martini) { - const size = martini.gridSize; - if (terrain.length !== size * size) { - throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); - } - this.terrain = terrain; - this.martini = martini; - this.errors = new Float32Array(terrain.length); - this.update(); - } - update() { - const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; - const { terrain, errors } = this; - for (let i = numTriangles - 1; i >= 0; i--) { - const k = i * 4; - const ax = coords[k + 0]; - const ay = coords[k + 1]; - const bx = coords[k + 2]; - const by = coords[k + 3]; - const mx = ax + bx >> 1; - const my = ay + by >> 1; - const cx = mx + my - ay; - const cy = my + ax - mx; - const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; - const middleIndex = my * size + mx; - const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); - errors[middleIndex] = Math.max(errors[middleIndex], middleError); - if (i < numParentTriangles) { - const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); - const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); - errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); - } - } - } - getMesh(maxError = 0, withSkirts = false) { - const { gridSize: size, indices } = this.martini; - const { errors } = this; - let numVertices = 0; - let numTriangles = 0; - const max = size - 1; - let aIndex, bIndex, cIndex = 0; - const leftSkirtIndices = []; - const rightSkirtIndices = []; - const bottomSkirtIndices = []; - const topSkirtIndices = []; - indices.fill(0); - function countElements(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - countElements(cx, cy, ax, ay, mx, my); - countElements(bx, by, cx, cy, mx, my); - } - else { - aIndex = ay * size + ax; - bIndex = by * size + bx; - cIndex = cy * size + cx; - if (indices[aIndex] === 0) { - if (withSkirts) { - if (ax === 0) { - leftSkirtIndices.push(numVertices); - } - else if (ax === max) { - rightSkirtIndices.push(numVertices); - } - if (ay === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (ay === max) { - topSkirtIndices.push(numVertices); - } - } - indices[aIndex] = ++numVertices; - } - if (indices[bIndex] === 0) { - if (withSkirts) { - if (bx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (bx === max) { - rightSkirtIndices.push(numVertices); - } - if (by === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (by === max) { - topSkirtIndices.push(numVertices); - } - } - indices[bIndex] = ++numVertices; - } - if (indices[cIndex] === 0) { - if (withSkirts) { - if (cx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (cx === max) { - rightSkirtIndices.push(numVertices); - } - if (cy === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (cy === max) { - topSkirtIndices.push(numVertices); - } - } - indices[cIndex] = ++numVertices; - } - numTriangles++; - } - } - countElements(0, 0, max, max, max, 0); - countElements(max, max, 0, 0, 0, max); - let numTotalVertices = numVertices * 2; - let numTotalTriangles = numTriangles * 3; - if (withSkirts) { - numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; - numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; - } - const vertices = new Uint16Array(numTotalVertices); - const triangles = new Uint32Array(numTotalTriangles); - let triIndex = 0; - function processTriangle(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - processTriangle(cx, cy, ax, ay, mx, my); - processTriangle(bx, by, cx, cy, mx, my); - } - else { - const a = indices[ay * size + ax] - 1; - const b = indices[by * size + bx] - 1; - const c = indices[cy * size + cx] - 1; - vertices[2 * a] = ax; - vertices[2 * a + 1] = ay; - vertices[2 * b] = bx; - vertices[2 * b + 1] = by; - vertices[2 * c] = cx; - vertices[2 * c + 1] = cy; - triangles[triIndex++] = a; - triangles[triIndex++] = b; - triangles[triIndex++] = c; - } - } - processTriangle(0, 0, max, max, max, 0); - processTriangle(max, max, 0, 0, 0, max); - if (withSkirts) { - leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); - rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); - bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); - topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); - let skirtIndex = numVertices * 2; - function constructSkirt(skirt) { - const skirtLength = skirt.length; - for (let i = 0; i < skirtLength - 1; i++) { - const currIndex = skirt[i]; - const nextIndex = skirt[i + 1]; - const currentSkirt = skirtIndex / 2; - const nextSkirt = (skirtIndex + 2) / 2; - vertices[skirtIndex++] = vertices[2 * currIndex]; - vertices[skirtIndex++] = vertices[2 * currIndex + 1]; - triangles[triIndex++] = currIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextSkirt; - triangles[triIndex++] = nextIndex; - } - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; - } - constructSkirt(leftSkirtIndices); - constructSkirt(rightSkirtIndices); - constructSkirt(bottomSkirtIndices); - constructSkirt(topSkirtIndices); - } - return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; - } +class Martini { + constructor(gridSize = 257) { + this.gridSize = gridSize; + const tileSize = gridSize - 1; + if (tileSize & tileSize - 1) { + throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); + } + this.numTriangles = tileSize * tileSize * 2 - 2; + this.numParentTriangles = this.numTriangles - tileSize * tileSize; + this.indices = new Uint32Array(this.gridSize * this.gridSize); + this.coords = new Uint16Array(this.numTriangles * 4); + for (let i = 0; i < this.numTriangles; i++) { + let id = i + 2; + let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; + if (id & 1) { + bx = by = cx = tileSize; + } + else { + ax = ay = cy = tileSize; + } + while ((id >>= 1) > 1) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (id & 1) { + bx = ax; + by = ay; + ax = cx; + ay = cy; + } + else { + ax = bx; + ay = by; + bx = cx; + by = cy; + } + cx = mx; + cy = my; + } + const k = i * 4; + this.coords[k + 0] = ax; + this.coords[k + 1] = ay; + this.coords[k + 2] = bx; + this.coords[k + 3] = by; + } + } + createTile(terrain) { + return new Tile(terrain, this); + } +} +class Tile { + constructor(terrain, martini) { + const size = martini.gridSize; + if (terrain.length !== size * size) { + throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); + } + this.terrain = terrain; + this.martini = martini; + this.errors = new Float32Array(terrain.length); + this.update(); + } + update() { + const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; + const { terrain, errors } = this; + for (let i = numTriangles - 1; i >= 0; i--) { + const k = i * 4; + const ax = coords[k + 0]; + const ay = coords[k + 1]; + const bx = coords[k + 2]; + const by = coords[k + 3]; + const mx = ax + bx >> 1; + const my = ay + by >> 1; + const cx = mx + my - ay; + const cy = my + ax - mx; + const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; + const middleIndex = my * size + mx; + const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); + errors[middleIndex] = Math.max(errors[middleIndex], middleError); + if (i < numParentTriangles) { + const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); + const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); + errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); + } + } + } + getMesh(maxError = 0, withSkirts = false) { + const { gridSize: size, indices } = this.martini; + const { errors } = this; + let numVertices = 0; + let numTriangles = 0; + const max = size - 1; + let aIndex, bIndex, cIndex = 0; + const leftSkirtIndices = []; + const rightSkirtIndices = []; + const bottomSkirtIndices = []; + const topSkirtIndices = []; + indices.fill(0); + function countElements(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + countElements(cx, cy, ax, ay, mx, my); + countElements(bx, by, cx, cy, mx, my); + } + else { + aIndex = ay * size + ax; + bIndex = by * size + bx; + cIndex = cy * size + cx; + if (indices[aIndex] === 0) { + if (withSkirts) { + if (ax === 0) { + leftSkirtIndices.push(numVertices); + } + else if (ax === max) { + rightSkirtIndices.push(numVertices); + } + if (ay === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (ay === max) { + topSkirtIndices.push(numVertices); + } + } + indices[aIndex] = ++numVertices; + } + if (indices[bIndex] === 0) { + if (withSkirts) { + if (bx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (bx === max) { + rightSkirtIndices.push(numVertices); + } + if (by === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (by === max) { + topSkirtIndices.push(numVertices); + } + } + indices[bIndex] = ++numVertices; + } + if (indices[cIndex] === 0) { + if (withSkirts) { + if (cx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (cx === max) { + rightSkirtIndices.push(numVertices); + } + if (cy === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (cy === max) { + topSkirtIndices.push(numVertices); + } + } + indices[cIndex] = ++numVertices; + } + numTriangles++; + } + } + countElements(0, 0, max, max, max, 0); + countElements(max, max, 0, 0, 0, max); + let numTotalVertices = numVertices * 2; + let numTotalTriangles = numTriangles * 3; + if (withSkirts) { + numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; + numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; + } + const vertices = new Uint16Array(numTotalVertices); + const triangles = new Uint32Array(numTotalTriangles); + let triIndex = 0; + function processTriangle(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + processTriangle(cx, cy, ax, ay, mx, my); + processTriangle(bx, by, cx, cy, mx, my); + } + else { + const a = indices[ay * size + ax] - 1; + const b = indices[by * size + bx] - 1; + const c = indices[cy * size + cx] - 1; + vertices[2 * a] = ax; + vertices[2 * a + 1] = ay; + vertices[2 * b] = bx; + vertices[2 * b + 1] = by; + vertices[2 * c] = cx; + vertices[2 * c + 1] = cy; + triangles[triIndex++] = a; + triangles[triIndex++] = b; + triangles[triIndex++] = c; + } + } + processTriangle(0, 0, max, max, max, 0); + processTriangle(max, max, 0, 0, 0, max); + if (withSkirts) { + leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); + rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); + bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); + topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); + let skirtIndex = numVertices * 2; + function constructSkirt(skirt) { + const skirtLength = skirt.length; + for (let i = 0; i < skirtLength - 1; i++) { + const currIndex = skirt[i]; + const nextIndex = skirt[i + 1]; + const currentSkirt = skirtIndex / 2; + const nextSkirt = (skirtIndex + 2) / 2; + vertices[skirtIndex++] = vertices[2 * currIndex]; + vertices[skirtIndex++] = vertices[2 * currIndex + 1]; + triangles[triIndex++] = currIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextSkirt; + triangles[triIndex++] = nextIndex; + } + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; + } + constructSkirt(leftSkirtIndices); + constructSkirt(rightSkirtIndices); + constructSkirt(bottomSkirtIndices); + constructSkirt(topSkirtIndices); + } + return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; + } } -class MapMartiniHeightNode extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { - super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new MeshPhongMaterial({ - map: MapMartiniHeightNode.emptyTexture, - color: 0xFFFFFF, - side: DoubleSide - }), level, exageration)); - this.elevationDecoder = { - rScaler: 256, - gScaler: 1, - bScaler: 1 / 256, - offset: -32768 - }; - this.exageration = 1.0; - this.meshMaxError = 10; - if (elevationDecoder) { - this.elevationDecoder = elevationDecoder; - } - this.meshMaxError = meshMaxError; - this.exageration = exageration; - this.frustumCulled = false; - } - static prepareMaterial(material, level, exageration = 1.0) { - material.userData = { - heightMap: { value: MapMartiniHeightNode.emptyTexture }, - drawNormals: { value: 0 }, - drawBlack: { value: 0 }, - zoomlevel: { value: level }, - computeNormals: { value: 1 }, - drawTexture: { value: 1 } - }; - material.onBeforeCompile = (shader) => { - for (let i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = +class MapMartiniHeightNode extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { + super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new MeshPhongMaterial({ + map: MapMartiniHeightNode.emptyTexture, + color: 0xFFFFFF, + side: DoubleSide + }), level, exageration)); + this.elevationDecoder = { + rScaler: 256, + gScaler: 1, + bScaler: 1 / 256, + offset: -32768 + }; + this.exageration = 1.0; + this.meshMaxError = 10; + if (elevationDecoder) { + this.elevationDecoder = elevationDecoder; + } + this.meshMaxError = meshMaxError; + this.exageration = exageration; + this.frustumCulled = false; + } + static prepareMaterial(material, level, exageration = 1.0) { + material.userData = { + heightMap: { value: MapMartiniHeightNode.emptyTexture }, + drawNormals: { value: 0 }, + drawBlack: { value: 0 }, + zoomlevel: { value: level }, + computeNormals: { value: 1 }, + drawTexture: { value: 1 } + }; + material.onBeforeCompile = (shader) => { + for (let i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform bool computeNormals; uniform float zoomlevel; uniform sampler2D heightMap; - ` + shader.vertexShader; - shader.fragmentShader = + ` + shader.vertexShader; + shader.fragmentShader = ` uniform bool drawNormals; uniform bool drawTexture; uniform bool drawBlack; - ` + shader.fragmentShader; + ` + shader.fragmentShader; shader.fragmentShader = shader.fragmentShader.replace('#include ', ` if(drawBlack) { gl_FragColor = vec4( 0.0,0.0,0.0, 1.0 ); @@ -1159,7 +1248,7 @@ class MapMartiniHeightNode extends MapHeightNode { gl_FragColor = vec4( ( 0.5 * vNormal + 0.5 ), 1.0 ); } else if (!drawTexture) { gl_FragColor = vec4( 0.0,0.0,0.0, 0.0 ); - }`); + }`); shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -1202,710 +1291,710 @@ class MapMartiniHeightNode extends MapHeightNode { v2.z = (e+ h + i + f) / 4.0; vNormal = (normalize(cross(v2 - v0, v1 - v0))).rbg; } - `); - }; - return material; - } - static getTerrain(imageData, tileSize, elevation) { - const { rScaler, bScaler, gScaler, offset } = elevation; - const gridSize = tileSize + 1; - const terrain = new Float32Array(gridSize * gridSize); - for (let i = 0, y = 0; y < tileSize; y++) { - for (let x = 0; x < tileSize; x++, i++) { - const k = i * 4; - const r = imageData[k + 0]; - const g = imageData[k + 1]; - const b = imageData[k + 2]; - terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; - } - } - for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { - terrain[i] = terrain[i - gridSize]; - } - for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { - terrain[i] = terrain[i - 1]; - } - return terrain; - } - static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { - const gridSize = tileSize + 1; - const numOfVerticies = vertices.length / 2; - const positions = new Float32Array(numOfVerticies * 3); - const texCoords = new Float32Array(numOfVerticies * 2); - const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; - const xScale = (maxX - minX) / tileSize; - const yScale = (maxY - minY) / tileSize; - for (let i = 0; i < numOfVerticies; i++) { - const x = vertices[i * 2]; - const y = vertices[i * 2 + 1]; - const pixelIdx = y * gridSize + x; - positions[3 * i + 0] = x * xScale + minX; - positions[3 * i + 1] = -terrain[pixelIdx] * exageration; - positions[3 * i + 2] = -y * yScale + maxY; - texCoords[2 * i + 0] = x / tileSize; - texCoords[2 * i + 1] = y / tileSize; - } - return { - position: { value: positions, size: 3 }, - uv: { value: texCoords, size: 2 } - }; - } - processHeight(image) { - return __awaiter(this, void 0, void 0, function* () { - const tileSize = image.width; - const gridSize = tileSize + 1; - var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); - var context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); - const martini = new Martini(gridSize); - const tile = martini.createTile(terrain); - const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); - const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); - this.geometry = new BufferGeometry(); - this.geometry.setIndex(new Uint32BufferAttribute(triangles, 1)); - this.geometry.setAttribute('position', new Float32BufferAttribute(attributes.position.value, attributes.position.size)); - this.geometry.setAttribute('uv', new Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); - this.geometry.rotateX(Math.PI); - var texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = NearestFilter; - texture.minFilter = NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - this.material.map = texture; - this.material.needsUpdate = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - this.processHeight(image); - this.heightLoaded = true; - this.nodeReady(); - }); - } -} -MapMartiniHeightNode.geometrySize = 16; -MapMartiniHeightNode.emptyTexture = new Texture(); -MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + `); + }; + return material; + } + static getTerrain(imageData, tileSize, elevation) { + const { rScaler, bScaler, gScaler, offset } = elevation; + const gridSize = tileSize + 1; + const terrain = new Float32Array(gridSize * gridSize); + for (let i = 0, y = 0; y < tileSize; y++) { + for (let x = 0; x < tileSize; x++, i++) { + const k = i * 4; + const r = imageData[k + 0]; + const g = imageData[k + 1]; + const b = imageData[k + 2]; + terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; + } + } + for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { + terrain[i] = terrain[i - gridSize]; + } + for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { + terrain[i] = terrain[i - 1]; + } + return terrain; + } + static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { + const gridSize = tileSize + 1; + const numOfVerticies = vertices.length / 2; + const positions = new Float32Array(numOfVerticies * 3); + const texCoords = new Float32Array(numOfVerticies * 2); + const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; + const xScale = (maxX - minX) / tileSize; + const yScale = (maxY - minY) / tileSize; + for (let i = 0; i < numOfVerticies; i++) { + const x = vertices[i * 2]; + const y = vertices[i * 2 + 1]; + const pixelIdx = y * gridSize + x; + positions[3 * i + 0] = x * xScale + minX; + positions[3 * i + 1] = -terrain[pixelIdx] * exageration; + positions[3 * i + 2] = -y * yScale + maxY; + texCoords[2 * i + 0] = x / tileSize; + texCoords[2 * i + 1] = y / tileSize; + } + return { + position: { value: positions, size: 3 }, + uv: { value: texCoords, size: 2 } + }; + } + processHeight(image) { + return __awaiter(this, void 0, void 0, function* () { + const tileSize = image.width; + const gridSize = tileSize + 1; + var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); + var context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); + var imageData = context.getImageData(0, 0, canvas.width, canvas.height); + var data = imageData.data; + const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); + const martini = new Martini(gridSize); + const tile = martini.createTile(terrain); + const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); + const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); + this.geometry = new BufferGeometry(); + this.geometry.setIndex(new Uint32BufferAttribute(triangles, 1)); + this.geometry.setAttribute('position', new Float32BufferAttribute(attributes.position.value, attributes.position.size)); + this.geometry.setAttribute('uv', new Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); + this.geometry.rotateX(Math.PI); + var texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + this.material.map = texture; + this.material.needsUpdate = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + this.processHeight(image); + this.heightLoaded = true; + this.nodeReady(); + }); + } +} +MapMartiniHeightNode.geometrySize = 16; +MapMartiniHeightNode.emptyTexture = new Texture(); +MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); MapMartiniHeightNode.tileSize = 256; -class MapView extends Mesh { - constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new MeshBasicMaterial({ transparent: true, opacity: 0.0 })); - this.lod = null; - this.provider = null; - this.heightProvider = null; - this.root = null; - this.cacheTiles = false; - this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { - this.lod.updateLOD(this, camera, renderer, scene); - }; - this.lod = new LODRaycast(); - this.provider = provider; - this.heightProvider = heightProvider; - this.setRoot(root); - this.preSubdivide(); - } - setRoot(root) { - if (typeof root === 'number') { - if (!MapView.mapModes.has(root)) { - throw new Error('Map mode ' + root + ' does is not registered.'); - } - const rootConstructor = MapView.mapModes.get(root); - root = new rootConstructor(null, this); - } - if (this.root !== null) { - this.remove(this.root); - this.root = null; - } - this.root = root; - if (this.root !== null) { - this.geometry = this.root.constructor.baseGeometry; - this.scale.copy(this.root.constructor.baseScale); - this.root.mapView = this; - this.add(this.root); - this.root.initialize(); - } - } - preSubdivide() { - var _a, _b; - function subdivide(node, depth) { - if (depth <= 0) { - return; - } - node.subdivide(); - for (let i = 0; i < node.children.length; i++) { - if (node.children[i] instanceof MapNode) { - const child = node.children[i]; - subdivide(child, depth - 1); - } - } - } - const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - if (minZoom > 0) { - subdivide(this.root, minZoom); - } - } - setProvider(provider) { - if (provider !== this.provider) { - this.provider = provider; - this.clear(); - } - } - setHeightProvider(heightProvider) { - if (heightProvider !== this.heightProvider) { - this.heightProvider = heightProvider; - this.clear(); - } - } - clear() { - this.traverse(function (children) { - if (children.childrenCache) { - children.childrenCache = null; - } - if (children.initialize) { - children.initialize(); - } - }); - return this; - } - minZoom() { - var _a, _b; - return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - } - maxZoom() { - var _a, _b; - return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); - } - getMetaData() { - this.provider.getMetaData(); - } - raycast(raycaster, intersects) { - return false; - } -} -MapView.PLANAR = 200; -MapView.SPHERICAL = 201; -MapView.HEIGHT = 202; -MapView.HEIGHT_SHADER = 203; -MapView.MARTINI = 204; -MapView.mapModes = new Map([ - [MapView.PLANAR, MapPlaneNode], - [MapView.SPHERICAL, MapSphereNode], - [MapView.HEIGHT, MapHeightNode], - [MapView.HEIGHT_SHADER, MapHeightNodeShader], - [MapView.MARTINI, MapMartiniHeightNode] +class MapView extends Mesh { + constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { + super(undefined, new MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); + this.lod = null; + this.provider = null; + this.heightProvider = null; + this.root = null; + this.cacheTiles = false; + this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { + this.lod.updateLOD(this, camera, renderer, scene); + }; + this.lod = new LODRaycast(); + this.provider = provider; + this.heightProvider = heightProvider; + this.setRoot(root); + this.preSubdivide(); + } + setRoot(root) { + if (typeof root === 'number') { + if (!MapView.mapModes.has(root)) { + throw new Error('Map mode ' + root + ' does is not registered.'); + } + const rootConstructor = MapView.mapModes.get(root); + root = new rootConstructor(null, this); + } + if (this.root !== null) { + this.remove(this.root); + this.root = null; + } + this.root = root; + if (this.root !== null) { + this.geometry = this.root.constructor.baseGeometry; + this.scale.copy(this.root.constructor.baseScale); + this.root.mapView = this; + this.add(this.root); + this.root.initialize(); + } + } + preSubdivide() { + var _a, _b; + function subdivide(node, depth) { + if (depth <= 0) { + return; + } + node.subdivide(); + for (let i = 0; i < node.children.length; i++) { + if (node.children[i] instanceof MapNode) { + const child = node.children[i]; + subdivide(child, depth - 1); + } + } + } + const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + if (minZoom > 0) { + subdivide(this.root, minZoom); + } + } + setProvider(provider) { + if (provider !== this.provider) { + this.provider = provider; + this.clear(); + } + } + setHeightProvider(heightProvider) { + if (heightProvider !== this.heightProvider) { + this.heightProvider = heightProvider; + this.clear(); + } + } + clear() { + this.traverse(function (children) { + if (children.childrenCache) { + children.childrenCache = null; + } + if (children.initialize) { + children.initialize(); + } + }); + return this; + } + minZoom() { + var _a, _b; + return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + } + maxZoom() { + var _a, _b; + return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); + } + getMetaData() { + this.provider.getMetaData(); + } + raycast(raycaster, intersects) { + return false; + } +} +MapView.PLANAR = 200; +MapView.SPHERICAL = 201; +MapView.HEIGHT = 202; +MapView.HEIGHT_SHADER = 203; +MapView.MARTINI = 204; +MapView.mapModes = new Map([ + [MapView.PLANAR, MapPlaneNode], + [MapView.SPHERICAL, MapSphereNode], + [MapView.HEIGHT, MapHeightNode], + [MapView.HEIGHT_SHADER, MapHeightNodeShader], + [MapView.MARTINI, MapMartiniHeightNode] ]); -const pov$1 = new Vector3(); -const position$1 = new Vector3(); -class LODRadial { - constructor(subdivideDistance = 50, simplifyDistance = 300) { - this.subdivideDistance = subdivideDistance; - this.simplifyDistance = simplifyDistance; - } - updateLOD(view, camera, renderer, scene) { - camera.getWorldPosition(pov$1); - view.children[0].traverse((node) => { - node.getWorldPosition(position$1); - let distance = pov$1.distanceTo(position$1); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - if (distance < this.subdivideDistance) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } +const pov$1 = new Vector3(); +const position$1 = new Vector3(); +class LODRadial { + constructor(subdivideDistance = 50, simplifyDistance = 300) { + this.subdivideDistance = subdivideDistance; + this.simplifyDistance = simplifyDistance; + } + updateLOD(view, camera, renderer, scene) { + camera.getWorldPosition(pov$1); + view.children[0].traverse((node) => { + node.getWorldPosition(position$1); + let distance = pov$1.distanceTo(position$1); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + if (distance < this.subdivideDistance) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } -const projection = new Matrix4(); -const pov = new Vector3(); -const frustum = new Frustum(); -const position = new Vector3(); -class LODFrustum extends LODRadial { - constructor(subdivideDistance = 120, simplifyDistance = 400) { - super(subdivideDistance, simplifyDistance); - this.testCenter = true; - this.pointOnly = false; - } - updateLOD(view, camera, renderer, scene) { - projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromProjectionMatrix(projection); - camera.getWorldPosition(pov); - view.children[0].traverse((node) => { - node.getWorldPosition(position); - let distance = pov.distanceTo(position); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); - if (distance < this.subdivideDistance && inFrustum) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } +const projection = new Matrix4(); +const pov = new Vector3(); +const frustum = new Frustum(); +const position = new Vector3(); +class LODFrustum extends LODRadial { + constructor(subdivideDistance = 120, simplifyDistance = 400) { + super(subdivideDistance, simplifyDistance); + this.testCenter = true; + this.pointOnly = false; + } + updateLOD(view, camera, renderer, scene) { + projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projection); + camera.getWorldPosition(pov); + view.children[0].traverse((node) => { + node.getWorldPosition(position); + let distance = pov.distanceTo(position); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); + if (distance < this.subdivideDistance && inFrustum) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } -class XHRUtils { - static get(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static getRaw(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static request(url, type, header, body, onLoad, onError, onProgress) { - function parseResponse(response) { - try { - return JSON.parse(response); - } - catch (e) { - return response; - } - } - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open(type, url, true); - if (header !== null && header !== undefined) { - for (const i in header) { - xhr.setRequestHeader(i, header[i]); - } - } - if (onLoad !== undefined) { - xhr.onload = function (event) { - onLoad(parseResponse(xhr.response), xhr); - }; - } - if (onError !== undefined) { - xhr.onerror = onError; - } - if (onProgress !== undefined) { - xhr.onprogress = onProgress; - } - xhr.send(body !== undefined ? body : null); - return xhr; - } +class XHRUtils { + static get(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static getRaw(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static request(url, type, header, body, onLoad, onError, onProgress) { + function parseResponse(response) { + try { + return JSON.parse(response); + } + catch (e) { + return response; + } + } + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open(type, url, true); + if (header !== null && header !== undefined) { + for (const i in header) { + xhr.setRequestHeader(i, header[i]); + } + } + if (onLoad !== undefined) { + xhr.onload = function (event) { + onLoad(parseResponse(xhr.response), xhr); + }; + } + if (onError !== undefined) { + xhr.onerror = onError; + } + if (onProgress !== undefined) { + xhr.onprogress = onProgress; + } + xhr.send(body !== undefined ? body : null); + return xhr; + } } -class BingMapsProvider extends MapProvider { - constructor(apiKey = '', type = BingMapsProvider.AERIAL) { - super(); - this.maxZoom = 19; - this.minZoom = 1; - this.format = 'jpeg'; - this.mapSize = 512; - this.subdomain = 't1'; - this.meta = null; - this.apiKey = apiKey; - this.type = type; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; - const data = yield XHRUtils.get(address); - this.meta = JSON.parse(data); - }); - } - static quadKey(zoom, x, y) { - let quad = ''; - for (let i = zoom; i > 0; i--) { - const mask = 1 << i - 1; - let cell = 0; - if ((x & mask) !== 0) { - cell++; - } - if ((y & mask) !== 0) { - cell += 2; - } - quad += cell; - } - return quad; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; - }); - } -} -BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; -BingMapsProvider.AERIAL = 'a'; -BingMapsProvider.ROAD = 'r'; -BingMapsProvider.AERIAL_LABELS = 'h'; -BingMapsProvider.OBLIQUE = 'o'; +class BingMapsProvider extends MapProvider { + constructor(apiKey = '', type = BingMapsProvider.AERIAL) { + super(); + this.maxZoom = 19; + this.minZoom = 1; + this.format = 'jpeg'; + this.mapSize = 512; + this.subdomain = 't1'; + this.meta = null; + this.apiKey = apiKey; + this.type = type; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; + const data = yield XHRUtils.get(address); + this.meta = JSON.parse(data); + }); + } + static quadKey(zoom, x, y) { + let quad = ''; + for (let i = zoom; i > 0; i--) { + const mask = 1 << i - 1; + let cell = 0; + if ((x & mask) !== 0) { + cell++; + } + if ((y & mask) !== 0) { + cell += 2; + } + quad += cell; + } + return quad; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; + }); + } +} +BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; +BingMapsProvider.AERIAL = 'a'; +BingMapsProvider.ROAD = 'r'; +BingMapsProvider.AERIAL_LABELS = 'h'; +BingMapsProvider.OBLIQUE = 'o'; BingMapsProvider.OBLIQUE_LABELS = 'b'; -class GoogleMapsProvider extends MapProvider { - constructor(apiToken) { - super(); - this.sessionToken = null; - this.orientation = 0; - this.format = 'png'; - this.mapType = 'roadmap'; - this.overlay = false; - this.apiToken = apiToken !== undefined ? apiToken : ''; - this.createSession(); - } - createSession() { - const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; - const data = JSON.stringify({ - mapType: this.mapType, - language: 'en-EN', - region: 'en', - layerTypes: ['layerRoadmap', 'layerStreetview'], - overlay: this.overlay, - scale: 'scaleFactor1x' - }); - XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { - this.sessionToken = response.session; - }, function (xhr) { - throw new Error('Unable to create a google maps session.'); - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; - }); - } +class GoogleMapsProvider extends MapProvider { + constructor(apiToken) { + super(); + this.sessionToken = null; + this.orientation = 0; + this.format = 'png'; + this.mapType = 'roadmap'; + this.overlay = false; + this.apiToken = apiToken !== undefined ? apiToken : ''; + this.createSession(); + } + createSession() { + const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; + const data = JSON.stringify({ + mapType: this.mapType, + language: 'en-EN', + region: 'en', + layerTypes: ['layerRoadmap', 'layerStreetview'], + overlay: this.overlay, + scale: 'scaleFactor1x' + }); + XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + this.sessionToken = response.session; + }, function (xhr) { + throw new Error('Unable to create a google maps session.'); + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + }); + } } -class HereMapsProvider extends MapProvider { - constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { - super(); - this.appId = appId; - this.appCode = appCode; - this.style = style; - this.scheme = scheme; - this.format = format; - this.size = size; - this.version = 'newest'; - this.server = 1; - } - nextServer() { - this.server = this.server % 4 === 0 ? 1 : this.server + 1; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } - fetchTile(zoom, x, y) { - this.nextServer(); - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + - this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + - this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; - }); - } -} +class HereMapsProvider extends MapProvider { + constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { + super(); + this.appId = appId; + this.appCode = appCode; + this.style = style; + this.scheme = scheme; + this.format = format; + this.size = size; + this.version = 'newest'; + this.server = 1; + } + nextServer() { + this.server = this.server % 4 === 0 ? 1 : this.server + 1; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } + fetchTile(zoom, x, y) { + this.nextServer(); + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + + this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + + this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; + }); + } +} HereMapsProvider.PATH = '/maptile/2.1/'; -class MapBoxProvider extends MapProvider { - constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { - super(); - this.apiToken = apiToken; - this.format = format; - this.useHDPI = useHDPI; - this.mode = mode; - this.mapId = id; - this.style = id; - this.version = version; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - if (this.mode === MapBoxProvider.STYLE) { - image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; - } - else { - image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; - } - }); - } -} -MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; -MapBoxProvider.STYLE = 100; +class MapBoxProvider extends MapProvider { + constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { + super(); + this.apiToken = apiToken; + this.format = format; + this.useHDPI = useHDPI; + this.mode = mode; + this.mapId = id; + this.style = id; + this.version = version; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + if (this.mode === MapBoxProvider.STYLE) { + image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; + } + else { + image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; + } + }); + } +} +MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; +MapBoxProvider.STYLE = 100; MapBoxProvider.MAP_ID = 101; -class MapTilerProvider extends MapProvider { - constructor(apiKey, category, style, format) { - super(); - this.apiKey = apiKey !== undefined ? apiKey : ''; - this.format = format !== undefined ? format : 'png'; - this.category = category !== undefined ? category : 'maps'; - this.style = style !== undefined ? style : 'satellite'; - this.resolution = 512; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; - }); - } +class MapTilerProvider extends MapProvider { + constructor(apiKey, category, style, format) { + super(); + this.apiKey = apiKey !== undefined ? apiKey : ''; + this.format = format !== undefined ? format : 'png'; + this.category = category !== undefined ? category : 'maps'; + this.style = style !== undefined ? style : 'satellite'; + this.resolution = 512; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; + }); + } } -class OpenMapTilesProvider extends MapProvider { - constructor(address, format = 'png', theme = 'klokantech-basic') { - super(); - this.address = address; - this.format = format; - this.theme = theme; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = this.address + 'styles/' + this.theme + '.json'; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.format = meta.format; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } +class OpenMapTilesProvider extends MapProvider { + constructor(address, format = 'png', theme = 'klokantech-basic') { + super(); + this.address = address; + this.format = format; + this.theme = theme; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = this.address + 'styles/' + this.theme + '.json'; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.format = meta.format; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } -class DebugProvider extends MapProvider { - constructor() { - super(...arguments); - this.resolution = 256; - } - fetchTile(zoom, x, y) { - const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); - const context = canvas.getContext('2d'); - const green = new Color(0x00ff00); - const red = new Color(0xff0000); - const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); - context.fillStyle = color.getStyle(); - context.fillRect(0, 0, this.resolution, this.resolution); - context.fillStyle = '#000000'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; - context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); - context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); - return Promise.resolve(canvas); - } +class DebugProvider extends MapProvider { + constructor() { + super(...arguments); + this.resolution = 256; + } + fetchTile(zoom, x, y) { + const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); + const context = canvas.getContext('2d'); + const green = new Color(0x00ff00); + const red = new Color(0xff0000); + const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); + context.fillStyle = color.getStyle(); + context.fillRect(0, 0, this.resolution, this.resolution); + context.fillStyle = '#000000'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; + context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); + context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); + return Promise.resolve(canvas); + } } -class HeightDebugProvider extends MapProvider { - constructor(provider) { - super(); - this.fromColor = new Color(0xff0000); - this.toColor = new Color(0x00ff00); - this.provider = provider; - } - fetchTile(zoom, x, y) { - return __awaiter(this, void 0, void 0, function* () { - const image = yield this.provider.fetchTile(zoom, x, y); - const resolution = 256; - const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); - const imageData = context.getImageData(0, 0, resolution, resolution); - const data = imageData.data; - for (let i = 0; i < data.length; i += 4) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - const max = 1667721.6; - const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); - data[i] = color.r * 255; - data[i + 1] = color.g * 255; - data[i + 2] = color.b * 255; - } - context.putImageData(imageData, 0, 0); - return canvas; - }); - } +class HeightDebugProvider extends MapProvider { + constructor(provider) { + super(); + this.fromColor = new Color(0xff0000); + this.toColor = new Color(0x00ff00); + this.provider = provider; + } + fetchTile(zoom, x, y) { + return __awaiter(this, void 0, void 0, function* () { + const image = yield this.provider.fetchTile(zoom, x, y); + const resolution = 256; + const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); + const imageData = context.getImageData(0, 0, resolution, resolution); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + const max = 1667721.6; + const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); + data[i] = color.r * 255; + data[i + 1] = color.g * 255; + data[i + 2] = color.b * 255; + } + context.putImageData(imageData, 0, 0); + return canvas; + }); + } } -class GeolocationUtils { - static get() { - return new Promise(function (resolve, reject) { - navigator.geolocation.getCurrentPosition(function (result) { - resolve(result); - }, reject); - }); - } +class GeolocationUtils { + static get() { + return new Promise(function (resolve, reject) { + navigator.geolocation.getCurrentPosition(function (result) { + resolve(result); + }, reject); + }); + } } -class CancelablePromise { - constructor(executor) { - this.fulfilled = false; - this.rejected = false; - this.called = false; - const resolve = (v) => { - this.fulfilled = true; - this.value = v; - if (typeof this.onResolve === 'function') { - this.onResolve(this.value); - this.called = true; - } - }; - const reject = (reason) => { - this.rejected = true; - this.value = reason; - if (typeof this.onReject === 'function') { - this.onReject(this.value); - this.called = true; - } - }; - try { - executor(resolve, reject); - } - catch (error) { - reject(error); - } - } - cancel() { - return false; - } - then(callback) { - this.onResolve = callback; - if (this.fulfilled && !this.called) { - this.called = true; - this.onResolve(this.value); - } - return this; - } - catch(callback) { - this.onReject = callback; - if (this.rejected && !this.called) { - this.called = true; - this.onReject(this.value); - } - return this; - } - finally(callback) { - return this; - } - static resolve(val) { - return new CancelablePromise(function executor(resolve, _reject) { - resolve(val); - }); - } - static reject(reason) { - return new CancelablePromise(function executor(resolve, reject) { - reject(reason); - }); - } - static all(promises) { - const fulfilledPromises = []; - const result = []; - function executor(resolve, reject) { - promises.forEach((promise, index) => { - return promise - .then((val) => { - fulfilledPromises.push(true); - result[index] = val; - if (fulfilledPromises.length === promises.length) { - return resolve(result); - } - }) - .catch((error) => { return reject(error); }); - }); - } - return new CancelablePromise(executor); - } +class CancelablePromise { + constructor(executor) { + this.fulfilled = false; + this.rejected = false; + this.called = false; + const resolve = (v) => { + this.fulfilled = true; + this.value = v; + if (typeof this.onResolve === 'function') { + this.onResolve(this.value); + this.called = true; + } + }; + const reject = (reason) => { + this.rejected = true; + this.value = reason; + if (typeof this.onReject === 'function') { + this.onReject(this.value); + this.called = true; + } + }; + try { + executor(resolve, reject); + } + catch (error) { + reject(error); + } + } + cancel() { + return false; + } + then(callback) { + this.onResolve = callback; + if (this.fulfilled && !this.called) { + this.called = true; + this.onResolve(this.value); + } + return this; + } + catch(callback) { + this.onReject = callback; + if (this.rejected && !this.called) { + this.called = true; + this.onReject(this.value); + } + return this; + } + finally(callback) { + return this; + } + static resolve(val) { + return new CancelablePromise(function executor(resolve, _reject) { + resolve(val); + }); + } + static reject(reason) { + return new CancelablePromise(function executor(resolve, reject) { + reject(reason); + }); + } + static all(promises) { + const fulfilledPromises = []; + const result = []; + function executor(resolve, reject) { + promises.forEach((promise, index) => { + return promise + .then((val) => { + fulfilledPromises.push(true); + result[index] = val; + if (fulfilledPromises.length === promises.length) { + return resolve(result); + } + }) + .catch((error) => { return reject(error); }); + }); + } + return new CancelablePromise(executor); + } } export { BingMapsProvider, CancelablePromise, CanvasUtils, DebugProvider, Geolocation, GeolocationUtils, GoogleMapsProvider, HeightDebugProvider, HereMapsProvider, LODFrustum, LODRadial, LODRaycast, MapBoxProvider, MapHeightNode, MapHeightNodeShader, MapNode, MapNodeGeometry, MapNodeHeightGeometry, MapPlaneNode, MapProvider, MapSphereNode, MapSphereNodeGeometry, MapTilerProvider, MapView, OpenMapTilesProvider, OpenStreetMapsProvider, QuadTreePosition, TextureUtils, UnitsUtils, XHRUtils }; diff --git a/build/nodes/MapNode.d.ts b/build/nodes/MapNode.d.ts index 307a794..af8035f 100644 --- a/build/nodes/MapNode.d.ts +++ b/build/nodes/MapNode.d.ts @@ -29,6 +29,7 @@ export declare abstract class MapNode extends Mesh { subdivide(): void; simplify(): void; loadData(): Promise; + applyTexture(image: HTMLImageElement): Promise; nodeReady(): void; dispose(): void; } diff --git a/build/nodes/MapSphereNode.d.ts b/build/nodes/MapSphereNode.d.ts index 3634afa..82abae3 100644 --- a/build/nodes/MapSphereNode.d.ts +++ b/build/nodes/MapSphereNode.d.ts @@ -8,6 +8,7 @@ export declare class MapSphereNode extends MapNode { constructor(parentNode?: any, mapView?: any, location?: number, level?: number, x?: number, y?: number); initialize(): Promise; static createGeometry(zoom: number, x: number, y: number): MapSphereNodeGeometry; + applyTexture(image: HTMLImageElement): Promise; applyScaleNode(): void; updateMatrix(): void; updateMatrixWorld(force?: boolean): void; diff --git a/build/utils/UnitsUtils.d.ts b/build/utils/UnitsUtils.d.ts index 801ab82..677b984 100644 --- a/build/utils/UnitsUtils.d.ts +++ b/build/utils/UnitsUtils.d.ts @@ -6,10 +6,15 @@ export declare class UnitsUtils { static EARTH_RADIUS_B: number; static EARTH_PERIMETER: number; static EARTH_ORIGIN: number; + static WEB_MERCATOR_MAX_EXTENT: number; static datumsToSpherical(latitude: number, longitude: number): Vector2; static sphericalToDatums(x: number, y: number): Geolocation; static quadtreeToDatums(zoom: number, x: number, y: number): Geolocation; static vectorToDatums(dir: Vector3): Geolocation; static datumsToVector(latitude: number, longitude: number): Vector3; static mapboxAltitude(color: Color): number; + static getTileSize(zoom: number): number; + static tileBounds(zoom: number, x: number, y: number): number[]; + static webMercatorToLatitude(zoom: number, y: number): number; + static webMercatorToLongitude(zoom: number, x: number): number; } diff --git a/examples/basic.js b/examples/basic.js index 3ba2d2b..bb5fe67 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -30611,6 +30611,376 @@ } + const Cache = { + + enabled: false, + + files: {}, + + add: function ( key, file ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Adding key:', key ); + + this.files[ key ] = file; + + }, + + get: function ( key ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Checking key:', key ); + + return this.files[ key ]; + + }, + + remove: function ( key ) { + + delete this.files[ key ]; + + }, + + clear: function () { + + this.files = {}; + + } + + }; + + class LoadingManager { + + constructor( onLoad, onProgress, onError ) { + + const scope = this; + + let isLoading = false; + let itemsLoaded = 0; + let itemsTotal = 0; + let urlModifier = undefined; + const handlers = []; + + // Refer to #5689 for the reason why we don't set .onStart + // in the constructor + + this.onStart = undefined; + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + itemsTotal ++; + + if ( isLoading === false ) { + + if ( scope.onStart !== undefined ) { + + scope.onStart( url, itemsLoaded, itemsTotal ); + + } + + } + + isLoading = true; + + }; + + this.itemEnd = function ( url ) { + + itemsLoaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, itemsLoaded, itemsTotal ); + + } + + if ( itemsLoaded === itemsTotal ) { + + isLoading = false; + + if ( scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + } + + }; + + this.itemError = function ( url ) { + + if ( scope.onError !== undefined ) { + + scope.onError( url ); + + } + + }; + + this.resolveURL = function ( url ) { + + if ( urlModifier ) { + + return urlModifier( url ); + + } + + return url; + + }; + + this.setURLModifier = function ( transform ) { + + urlModifier = transform; + + return this; + + }; + + this.addHandler = function ( regex, loader ) { + + handlers.push( regex, loader ); + + return this; + + }; + + this.removeHandler = function ( regex ) { + + const index = handlers.indexOf( regex ); + + if ( index !== - 1 ) { + + handlers.splice( index, 2 ); + + } + + return this; + + }; + + this.getHandler = function ( file ) { + + for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + + const regex = handlers[ i ]; + const loader = handlers[ i + 1 ]; + + if ( regex.global ) regex.lastIndex = 0; // see #17920 + + if ( regex.test( file ) ) { + + return loader; + + } + + } + + return null; + + }; + + } + + } + + const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + + class Loader { + + constructor( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + this.crossOrigin = 'anonymous'; + this.withCredentials = false; + this.path = ''; + this.resourcePath = ''; + this.requestHeader = {}; + + } + + load( /* url, onLoad, onProgress, onError */ ) {} + + loadAsync( url, onProgress ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.load( url, resolve, onProgress, reject ); + + } ); + + } + + parse( /* data */ ) {} + + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + setWithCredentials( value ) { + + this.withCredentials = value; + return this; + + } + + setPath( path ) { + + this.path = path; + return this; + + } + + setResourcePath( resourcePath ) { + + this.resourcePath = resourcePath; + return this; + + } + + setRequestHeader( requestHeader ) { + + this.requestHeader = requestHeader; + return this; + + } + + } + + Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; + + class ImageLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + const scope = this; + + const cached = Cache.get( url ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + const image = createElementNS( 'img' ); + + function onImageLoad() { + + removeEventListeners(); + + Cache.add( url, this ); + + if ( onLoad ) onLoad( this ); + + scope.manager.itemEnd( url ); + + } + + function onImageError( event ) { + + removeEventListeners(); + + if ( onError ) onError( event ); + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } + + function removeEventListeners() { + + image.removeEventListener( 'load', onImageLoad, false ); + image.removeEventListener( 'error', onImageError, false ); + + } + + image.addEventListener( 'load', onImageLoad, false ); + image.addEventListener( 'error', onImageError, false ); + + if ( url.slice( 0, 5 ) !== 'data:' ) { + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + } + + scope.manager.itemStart( url ); + + image.src = url; + + return image; + + } + + } + + class TextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const texture = new Texture(); + + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + loader.load( url, function ( image ) { + + texture.image = image; + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + } + + } + class Light extends Object3D { constructor( color, intensity = 1 ) { @@ -31358,6 +31728,8 @@ const pointers = []; const pointerPositions = {}; + let controlActive = false; + function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31374,8 +31746,8 @@ function getZoomScale( delta ) { - const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); - return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); } @@ -32072,12 +32444,70 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( event ); + handleMouseWheel( customWheelEvent( event ) ); scope.dispatchEvent( _endEvent ); } + function customWheelEvent( event ) { + + const mode = event.deltaMode; + + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; + + switch ( mode ) { + + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; + + } + + // detect if event was triggered by pinching + if ( event.ctrlKey && !controlActive ) { + + newEvent.deltaY *= 10; + + } + + return newEvent; + + } + + function interceptControlDown( event ) { + + if ( event.key === "Control" ) { + + controlActive = true; + + document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + + function interceptControlUp( event ) { + + if ( event.key === "Control" ) { + + controlActive = false; + + document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32286,6 +32716,8 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); + // force an update at start this.update(); @@ -32317,29 +32749,29 @@ } - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -32483,16 +32915,7 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; + yield this.applyTexture(image); } catch (e) { if (this.disposed) { @@ -32504,6 +32927,23 @@ this.material.needsUpdate = true; }); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new Texture(image); + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -32706,12 +33146,32 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; + UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -32989,7 +33449,48 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new Vector4(...bounds); + const material = new ShaderMaterial({ + uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33009,12 +33510,28 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -33169,7 +33686,11 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; diff --git a/examples/providers.js b/examples/providers.js index 8e12898..06dee81 100644 --- a/examples/providers.js +++ b/examples/providers.js @@ -30611,6 +30611,376 @@ } + const Cache = { + + enabled: false, + + files: {}, + + add: function ( key, file ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Adding key:', key ); + + this.files[ key ] = file; + + }, + + get: function ( key ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Checking key:', key ); + + return this.files[ key ]; + + }, + + remove: function ( key ) { + + delete this.files[ key ]; + + }, + + clear: function () { + + this.files = {}; + + } + + }; + + class LoadingManager { + + constructor( onLoad, onProgress, onError ) { + + const scope = this; + + let isLoading = false; + let itemsLoaded = 0; + let itemsTotal = 0; + let urlModifier = undefined; + const handlers = []; + + // Refer to #5689 for the reason why we don't set .onStart + // in the constructor + + this.onStart = undefined; + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + itemsTotal ++; + + if ( isLoading === false ) { + + if ( scope.onStart !== undefined ) { + + scope.onStart( url, itemsLoaded, itemsTotal ); + + } + + } + + isLoading = true; + + }; + + this.itemEnd = function ( url ) { + + itemsLoaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, itemsLoaded, itemsTotal ); + + } + + if ( itemsLoaded === itemsTotal ) { + + isLoading = false; + + if ( scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + } + + }; + + this.itemError = function ( url ) { + + if ( scope.onError !== undefined ) { + + scope.onError( url ); + + } + + }; + + this.resolveURL = function ( url ) { + + if ( urlModifier ) { + + return urlModifier( url ); + + } + + return url; + + }; + + this.setURLModifier = function ( transform ) { + + urlModifier = transform; + + return this; + + }; + + this.addHandler = function ( regex, loader ) { + + handlers.push( regex, loader ); + + return this; + + }; + + this.removeHandler = function ( regex ) { + + const index = handlers.indexOf( regex ); + + if ( index !== - 1 ) { + + handlers.splice( index, 2 ); + + } + + return this; + + }; + + this.getHandler = function ( file ) { + + for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + + const regex = handlers[ i ]; + const loader = handlers[ i + 1 ]; + + if ( regex.global ) regex.lastIndex = 0; // see #17920 + + if ( regex.test( file ) ) { + + return loader; + + } + + } + + return null; + + }; + + } + + } + + const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + + class Loader { + + constructor( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + this.crossOrigin = 'anonymous'; + this.withCredentials = false; + this.path = ''; + this.resourcePath = ''; + this.requestHeader = {}; + + } + + load( /* url, onLoad, onProgress, onError */ ) {} + + loadAsync( url, onProgress ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.load( url, resolve, onProgress, reject ); + + } ); + + } + + parse( /* data */ ) {} + + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + setWithCredentials( value ) { + + this.withCredentials = value; + return this; + + } + + setPath( path ) { + + this.path = path; + return this; + + } + + setResourcePath( resourcePath ) { + + this.resourcePath = resourcePath; + return this; + + } + + setRequestHeader( requestHeader ) { + + this.requestHeader = requestHeader; + return this; + + } + + } + + Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; + + class ImageLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + const scope = this; + + const cached = Cache.get( url ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + const image = createElementNS( 'img' ); + + function onImageLoad() { + + removeEventListeners(); + + Cache.add( url, this ); + + if ( onLoad ) onLoad( this ); + + scope.manager.itemEnd( url ); + + } + + function onImageError( event ) { + + removeEventListeners(); + + if ( onError ) onError( event ); + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } + + function removeEventListeners() { + + image.removeEventListener( 'load', onImageLoad, false ); + image.removeEventListener( 'error', onImageError, false ); + + } + + image.addEventListener( 'load', onImageLoad, false ); + image.addEventListener( 'error', onImageError, false ); + + if ( url.slice( 0, 5 ) !== 'data:' ) { + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + } + + scope.manager.itemStart( url ); + + image.src = url; + + return image; + + } + + } + + class TextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const texture = new Texture(); + + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + loader.load( url, function ( image ) { + + texture.image = image; + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + } + + } + class Light extends Object3D { constructor( color, intensity = 1 ) { @@ -31548,6 +31918,8 @@ const pointers = []; const pointerPositions = {}; + let controlActive = false; + function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31564,8 +31936,8 @@ function getZoomScale( delta ) { - const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); - return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); } @@ -32262,12 +32634,70 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( event ); + handleMouseWheel( customWheelEvent( event ) ); scope.dispatchEvent( _endEvent ); } + function customWheelEvent( event ) { + + const mode = event.deltaMode; + + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; + + switch ( mode ) { + + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; + + } + + // detect if event was triggered by pinching + if ( event.ctrlKey && !controlActive ) { + + newEvent.deltaY *= 10; + + } + + return newEvent; + + } + + function interceptControlDown( event ) { + + if ( event.key === "Control" ) { + + controlActive = true; + + document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + + function interceptControlUp( event ) { + + if ( event.key === "Control" ) { + + controlActive = false; + + document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32476,6 +32906,8 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); + // force an update at start this.update(); @@ -32716,29 +33148,29 @@ }; - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -32882,16 +33314,7 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; + yield this.applyTexture(image); } catch (e) { if (this.disposed) { @@ -32903,6 +33326,23 @@ this.material.needsUpdate = true; }); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new Texture(image); + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -33105,12 +33545,32 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; + UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -33388,7 +33848,48 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new Vector4(...bounds); + const material = new ShaderMaterial({ + uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33408,12 +33909,28 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -33568,7 +34085,11 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; diff --git a/examples/transition.js b/examples/transition.js index 444b6d6..d53a83c 100644 --- a/examples/transition.js +++ b/examples/transition.js @@ -31860,6 +31860,8 @@ const pointers = []; const pointerPositions = {}; + let controlActive = false; + function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31876,8 +31878,8 @@ function getZoomScale( delta ) { - const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); - return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); } @@ -32574,12 +32576,70 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( event ); + handleMouseWheel( customWheelEvent( event ) ); scope.dispatchEvent( _endEvent ); } + function customWheelEvent( event ) { + + const mode = event.deltaMode; + + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; + + switch ( mode ) { + + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; + + } + + // detect if event was triggered by pinching + if ( event.ctrlKey && !controlActive ) { + + newEvent.deltaY *= 10; + + } + + return newEvent; + + } + + function interceptControlDown( event ) { + + if ( event.key === "Control" ) { + + controlActive = true; + + document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + + function interceptControlUp( event ) { + + if ( event.key === "Control" ) { + + controlActive = false; + + document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); + + } + + } + function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32788,6 +32848,8 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); + // force an update at start this.update(); @@ -32819,29 +32881,29 @@ } - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -32985,16 +33047,7 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; + yield this.applyTexture(image); } catch (e) { if (this.disposed) { @@ -33006,6 +33059,23 @@ this.material.needsUpdate = true; }); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + if (this.disposed) { + return; + } + const texture = new Texture(image); + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + }); + } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -33208,12 +33278,32 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } + static getTileSize(zoom) { + const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; + const numTiles = Math.pow(2, zoom); + return 2 * maxExtent / numTiles; + } + static tileBounds(zoom, x, y) { + const tileSize = UnitsUtils.getTileSize(zoom); + const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; + const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; + return [minX, tileSize, minY, tileSize]; + } + static webMercatorToLatitude(zoom, y) { + const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); + return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); + } + static webMercatorToLongitude(zoom, x) { + const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); + return xMerc / UnitsUtils.EARTH_RADIUS; + } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; + UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -33491,7 +33581,48 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); + let bounds = UnitsUtils.tileBounds(level, x, y); + const vertexShader = ` + varying vec3 vPosition; + + void main() { + vPosition = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; + const fragmentShader = ` + #define PI 3.1415926538 + varying vec3 vPosition; + uniform sampler2D uTexture; + uniform vec4 webMercatorBounds; + + void main() { + // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom + float radius = length(vPosition); + + float latitude = asin(vPosition.y / radius); + float longitude = atan(-vPosition.z, vPosition.x); + + float web_mercator_x = radius * longitude; + float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); + float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; + float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; + + vec4 color = texture2D(uTexture, vec2(x, y)); + gl_FragColor = color; + ${parseInt(REVISION) < 152 ? '' : ` + #include + #include ${(parseInt(REVISION) >= 154) ? '' : ''} + `} + } + `; + let vBounds = new Vector4(...bounds); + const material = new ShaderMaterial({ + uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, + vertexShader: vertexShader, + fragmentShader: fragmentShader + }); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33511,12 +33642,28 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const phiLength = 1 / range * 2 * Math.PI; - const phiStart = x * phiLength; - const thetaLength = 1 / range * Math.PI; - const thetaStart = y * thetaLength; + const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; + const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; + const phiStart = lon1; + const phiLength = lon2 - lon1; + const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; + const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; + const thetaLength = lat1 - lat2; + const thetaStart = Math.PI - (lat1 + Math.PI / 2); return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } + applyTexture(image) { + return __awaiter(this, void 0, void 0, function* () { + const textureLoader = new TextureLoader(); + const texture = textureLoader.load(image.src, function () { + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } + }); + this.material.uniforms.uTexture.value = texture; + this.material.uniforms.uTexture.needsUpdate = true; + }); + } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -33671,7 +33818,11 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - this.raycaster.intersectObjects(view.children, true, intersects); + let myIntersects = []; + this.raycaster.intersectObjects(view.children, true, myIntersects); + if (myIntersects.length > 0) { + intersects.push(myIntersects[0]); + } } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; @@ -34450,6 +34601,9 @@ scene.background = new Color(0x000000, LinearSRGBColorSpace); var loader = new TextureLoader(); loader.load('2k_earth_daymap.jpg', function (texture) { + if (parseInt(REVISION) >= 152) { + texture.colorSpace = 'srgb'; + } var sphere = new Mesh(new SphereGeometry(UnitsUtils.EARTH_RADIUS, 256, 256), new MeshBasicMaterial({ map: texture })); scene.add(sphere); }); From 56231f89002e5f9cdc550416760c68c88a06e2bf Mon Sep 17 00:00:00 2001 From: rodri Date: Sun, 22 Sep 2024 07:41:39 -0400 Subject: [PATCH 3/5] Revert "update artifacts" This reverts commit 7998af512ac6401a2b757fb8eb5132c69da6f2c3. --- build/geo-three.cjs | 175 +- build/geo-three.js | 3593 ++++++++++++++++--------------- build/geo-three.module.js | 3595 ++++++++++++++++---------------- build/nodes/MapNode.d.ts | 1 - build/nodes/MapSphereNode.d.ts | 1 - build/utils/UnitsUtils.d.ts | 5 - examples/basic.js | 605 +----- examples/providers.js | 605 +----- examples/transition.js | 238 +-- 9 files changed, 3674 insertions(+), 5144 deletions(-) diff --git a/build/geo-three.cjs b/build/geo-three.cjs index ee935f9..6dc3285 100644 --- a/build/geo-three.cjs +++ b/build/geo-three.cjs @@ -2,29 +2,29 @@ var three = require('three'); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -168,7 +168,16 @@ class MapNode extends three.Mesh { } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; } catch (e) { if (this.disposed) { @@ -180,23 +189,6 @@ class MapNode extends three.Mesh { this.material.needsUpdate = true; }); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - if (parseInt(three.REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -399,32 +391,12 @@ class UnitsUtils { static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; -UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -702,48 +674,7 @@ class MapSphereNodeGeometry extends three.BufferGeometry { class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(three.REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(three.REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new three.Vector4(...bounds); - const material = new three.ShaderMaterial({ - uniforms: { uTexture: { value: new three.Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new three.MeshBasicMaterial({ wireframe: false })); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -763,28 +694,12 @@ class MapSphereNode extends MapNode { const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new three.TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(three.REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -939,11 +854,7 @@ class LODRaycast { for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } + this.raycaster.intersectObjects(view.children, true, intersects); } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; @@ -1394,7 +1305,7 @@ MapMartiniHeightNode.tileSize = 256; class MapView extends three.Mesh { constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); + super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0 })); this.lod = null; this.provider = null; this.heightProvider = null; @@ -1681,7 +1592,7 @@ class GoogleMapsProvider extends MapProvider { this.createSession(); } createSession() { - const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; + const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; const data = JSON.stringify({ mapType: this.mapType, language: 'en-EN', @@ -1690,7 +1601,7 @@ class GoogleMapsProvider extends MapProvider { overlay: this.overlay, scale: 'scaleFactor1x' }); - XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { this.sessionToken = response.session; }, function (xhr) { throw new Error('Unable to create a google maps session.'); @@ -1706,7 +1617,7 @@ class GoogleMapsProvider extends MapProvider { reject(); }; image.crossOrigin = 'Anonymous'; - image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; }); } } diff --git a/build/geo-three.js b/build/geo-three.js index be97347..7fa15dc 100644 --- a/build/geo-three.js +++ b/build/geo-three.js @@ -29,825 +29,740 @@ }); } - class MapProvider { - constructor() { - this.name = ''; - this.minZoom = 0; - this.maxZoom = 20; - this.bounds = []; - this.center = []; - } - fetchTile(zoom, x, y) { - return null; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } + class MapProvider { + constructor() { + this.name = ''; + this.minZoom = 0; + this.maxZoom = 20; + this.bounds = []; + this.center = []; + } + fetchTile(zoom, x, y) { + return null; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } } - class OpenStreetMapsProvider extends MapProvider { - constructor(address = 'https://a.tile.openstreetmap.org/') { - super(); - this.address = address; - this.format = 'png'; - this.maxZoom = 19; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } + class OpenStreetMapsProvider extends MapProvider { + constructor(address = 'https://a.tile.openstreetmap.org/') { + super(); + this.address = address; + this.format = 'png'; + this.maxZoom = 19; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } - class CanvasUtils { - static createOffscreenCanvas(width, height) { - if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(width, height); - } - else { - let canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - } + class CanvasUtils { + static createOffscreenCanvas(width, height) { + if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(width, height); + } + else { + let canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + } + } } - class TextureUtils { - static createFillTexture(color = '#000000', width = 1, height = 1) { - const canvas = CanvasUtils.createOffscreenCanvas(width, height); - const context = canvas.getContext('2d'); - context.fillStyle = color; - context.fillRect(0, 0, width, height); - const texture = new three.Texture(canvas); - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.generateMipmaps = false; - texture.needsUpdate = true; - return texture; - } + class TextureUtils { + static createFillTexture(color = '#000000', width = 1, height = 1) { + const canvas = CanvasUtils.createOffscreenCanvas(width, height); + const context = canvas.getContext('2d'); + context.fillStyle = color; + context.fillRect(0, 0, width, height); + const texture = new three.Texture(canvas); + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + return texture; + } } - class QuadTreePosition { - } - QuadTreePosition.root = -1; - QuadTreePosition.topLeft = 0; - QuadTreePosition.topRight = 1; - QuadTreePosition.bottomLeft = 2; - QuadTreePosition.bottomRight = 3; - class MapNode extends three.Mesh { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { - super(geometry, material); - this.mapView = null; - this.parentNode = null; - this.subdivided = false; - this.disposed = false; - this.nodesLoaded = 0; - this.childrenCache = null; - this.isMesh = true; - this.mapView = mapView; - this.parentNode = parentNode; - this.disposed = false; - this.location = location; - this.level = level; - this.x = x; - this.y = y; - this.initialize(); - } - initialize() { - return __awaiter(this, void 0, void 0, function* () { }); - } - createChildNodes() { } - subdivide() { - const maxZoom = this.mapView.maxZoom(); - if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { - return; - } - if (this.mapView.cacheTiles && this.childrenCache !== null) { - this.isMesh = false; - this.children = this.childrenCache; - this.nodesLoaded = this.childrenCache.length; - } - else { - this.createChildNodes(); - } - this.subdivided = true; - } - simplify() { - const minZoom = this.mapView.minZoom(); - if (this.level - 1 < minZoom) { - return; - } - if (this.mapView.cacheTiles) { - this.childrenCache = this.children; - } - else { - for (let i = 0; i < this.children.length; i++) { - this.children[i].dispose(); - } - } - this.subdivided = false; - this.isMesh = true; - this.children = []; - this.nodesLoaded = 0; - } - loadData() { - return __awaiter(this, void 0, void 0, function* () { - if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapNode.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); - } - catch (e) { - if (this.disposed) { - return; - } - console.warn('Geo-Three: Failed to load node tile data.', this); - this.material.map = MapNode.defaultTexture; - } - this.material.needsUpdate = true; - }); - } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - if (parseInt(three.REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.LinearFilter; - texture.minFilter = three.LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } - nodeReady() { - if (this.disposed) { - console.warn('Geo-Three: nodeReady() called for disposed node.', this); - this.dispose(); - return; - } - if (this.parentNode !== null) { - this.parentNode.nodesLoaded++; - if (this.parentNode.nodesLoaded === MapNode.childrens) { - if (this.parentNode.subdivided === true) { - this.parentNode.isMesh = false; - } - for (let i = 0; i < this.parentNode.children.length; i++) { - this.parentNode.children[i].visible = true; - } - } - if (this.parentNode.nodesLoaded > MapNode.childrens) { - console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); - } - } - else { - this.visible = true; - } - } - dispose() { - this.disposed = true; - const self = this; - try { - const material = self.material; - material.dispose(); - if (material.map && material.map !== MapNode.defaultTexture) { - material.map.dispose(); - } - } - catch (e) { } - try { - self.geometry.dispose(); - } - catch (e) { } - } - } - MapNode.defaultTexture = TextureUtils.createFillTexture(); - MapNode.baseGeometry = null; - MapNode.baseScale = null; + class QuadTreePosition { + } + QuadTreePosition.root = -1; + QuadTreePosition.topLeft = 0; + QuadTreePosition.topRight = 1; + QuadTreePosition.bottomLeft = 2; + QuadTreePosition.bottomRight = 3; + class MapNode extends three.Mesh { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { + super(geometry, material); + this.mapView = null; + this.parentNode = null; + this.subdivided = false; + this.disposed = false; + this.nodesLoaded = 0; + this.childrenCache = null; + this.isMesh = true; + this.mapView = mapView; + this.parentNode = parentNode; + this.disposed = false; + this.location = location; + this.level = level; + this.x = x; + this.y = y; + this.initialize(); + } + initialize() { + return __awaiter(this, void 0, void 0, function* () { }); + } + createChildNodes() { } + subdivide() { + const maxZoom = this.mapView.maxZoom(); + if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { + return; + } + if (this.mapView.cacheTiles && this.childrenCache !== null) { + this.isMesh = false; + this.children = this.childrenCache; + this.nodesLoaded = this.childrenCache.length; + } + else { + this.createChildNodes(); + } + this.subdivided = true; + } + simplify() { + const minZoom = this.mapView.minZoom(); + if (this.level - 1 < minZoom) { + return; + } + if (this.mapView.cacheTiles) { + this.childrenCache = this.children; + } + else { + for (let i = 0; i < this.children.length; i++) { + this.children[i].dispose(); + } + } + this.subdivided = false; + this.isMesh = true; + this.children = []; + this.nodesLoaded = 0; + } + loadData() { + return __awaiter(this, void 0, void 0, function* () { + if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapNode.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.LinearFilter; + texture.minFilter = three.LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.warn('Geo-Three: Failed to load node tile data.', this); + this.material.map = MapNode.defaultTexture; + } + this.material.needsUpdate = true; + }); + } + nodeReady() { + if (this.disposed) { + console.warn('Geo-Three: nodeReady() called for disposed node.', this); + this.dispose(); + return; + } + if (this.parentNode !== null) { + this.parentNode.nodesLoaded++; + if (this.parentNode.nodesLoaded === MapNode.childrens) { + if (this.parentNode.subdivided === true) { + this.parentNode.isMesh = false; + } + for (let i = 0; i < this.parentNode.children.length; i++) { + this.parentNode.children[i].visible = true; + } + } + if (this.parentNode.nodesLoaded > MapNode.childrens) { + console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); + } + } + else { + this.visible = true; + } + } + dispose() { + this.disposed = true; + const self = this; + try { + const material = self.material; + material.dispose(); + if (material.map && material.map !== MapNode.defaultTexture) { + material.map.dispose(); + } + } + catch (e) { } + try { + self.geometry.dispose(); + } + catch (e) { } + } + } + MapNode.defaultTexture = TextureUtils.createFillTexture(); + MapNode.baseGeometry = null; + MapNode.baseScale = null; MapNode.childrens = 4; - class MapNodeGeometry extends three.BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - } - static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - vertices.push(x, 0, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1 - iz / heightSegments); - } - } - for (let iz = 0; iz < heightSegments; iz++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix + gridX * iz; - const b = ix + gridX * (iz + 1); - const c = ix + 1 + gridX * (iz + 1); - const d = ix + 1 + gridX * iz; - indices.push(a, b, d, b, c, d); - } - } - } - static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - let start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = -heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1); - } - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix; - const d = ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(d, b, a, d, c, b); - } - start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = heightSegments * segmentHeight - heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 0); - } - let offset = gridX * gridZ - widthSegments - 1; - for (let ix = 0; ix < widthSegments; ix++) { - const a = offset + ix; - const d = offset + ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = -widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ; - const d = (iz + 1) * gridZ; - const b = iz + start; - const c = iz + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = widthSegments * segmentWidth - widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(1.0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ + heightSegments; - const d = (iz + 1) * gridZ + heightSegments; - const b = iz + start; - const c = iz + start + 1; - indices.push(d, b, a, d, c, b); - } - } + class MapNodeGeometry extends three.BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + } + static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + vertices.push(x, 0, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1 - iz / heightSegments); + } + } + for (let iz = 0; iz < heightSegments; iz++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix + gridX * iz; + const b = ix + gridX * (iz + 1); + const c = ix + 1 + gridX * (iz + 1); + const d = ix + 1 + gridX * iz; + indices.push(a, b, d, b, c, d); + } + } + } + static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + let start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = -heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1); + } + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix; + const d = ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(d, b, a, d, c, b); + } + start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = heightSegments * segmentHeight - heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 0); + } + let offset = gridX * gridZ - widthSegments - 1; + for (let ix = 0; ix < widthSegments; ix++) { + const a = offset + ix; + const d = offset + ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = -widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ; + const d = (iz + 1) * gridZ; + const b = iz + start; + const c = iz + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = widthSegments * segmentWidth - widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(1.0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ + heightSegments; + const d = (iz + 1) * gridZ + heightSegments; + const b = iz + start; + const c = iz + start + 1; + indices.push(d, b, a, d, c, b); + } + } } - class Geolocation { - constructor(latitude, longitude) { - this.latitude = latitude; - this.longitude = longitude; - } + class Geolocation { + constructor(latitude, longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } - class UnitsUtils { - static datumsToSpherical(latitude, longitude) { - const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; - let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); - y = y * UnitsUtils.EARTH_ORIGIN / 180.0; - return new three.Vector2(x, y); - } - static sphericalToDatums(x, y) { - const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; - let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; - latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); - return new Geolocation(latitude, longitude); - } - static quadtreeToDatums(zoom, x, y) { - const n = Math.pow(2.0, zoom); - const longitude = x / n * 360.0 - 180.0; - const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); - const latitude = 180.0 * (latitudeRad / Math.PI); - return new Geolocation(latitude, longitude); - } - static vectorToDatums(dir) { - const radToDeg = 180 / Math.PI; - const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; - const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; - return new Geolocation(latitude, longitude); - } - static datumsToVector(latitude, longitude) { - const degToRad = Math.PI / 180; - const rotX = longitude * degToRad; - const rotY = latitude * degToRad; - var cos = Math.cos(rotY); - return new three.Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); - } - static mapboxAltitude(color) { - return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; - } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } - } - UnitsUtils.EARTH_RADIUS = 6371008; - UnitsUtils.EARTH_RADIUS_A = 6378137.0; - UnitsUtils.EARTH_RADIUS_B = 6356752.314245; - UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; + class UnitsUtils { + static datumsToSpherical(latitude, longitude) { + const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; + let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); + y = y * UnitsUtils.EARTH_ORIGIN / 180.0; + return new three.Vector2(x, y); + } + static sphericalToDatums(x, y) { + const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; + let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; + latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); + return new Geolocation(latitude, longitude); + } + static quadtreeToDatums(zoom, x, y) { + const n = Math.pow(2.0, zoom); + const longitude = x / n * 360.0 - 180.0; + const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); + const latitude = 180.0 * (latitudeRad / Math.PI); + return new Geolocation(latitude, longitude); + } + static vectorToDatums(dir) { + const radToDeg = 180 / Math.PI; + const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; + const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; + return new Geolocation(latitude, longitude); + } + static datumsToVector(latitude, longitude) { + const degToRad = Math.PI / 180; + const rotX = longitude * degToRad; + const rotY = latitude * degToRad; + var cos = Math.cos(rotY); + return new three.Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); + } + static mapboxAltitude(color) { + return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; + } + } + UnitsUtils.EARTH_RADIUS = 6371008; + UnitsUtils.EARTH_RADIUS_A = 6378137.0; + UnitsUtils.EARTH_RADIUS_B = 6356752.314245; + UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; - UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; - class MapPlaneNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new three.MeshBasicMaterial({ wireframe: false })); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); - MapPlaneNode.baseGeometry = MapPlaneNode.geometry; + class MapPlaneNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new three.MeshBasicMaterial({ wireframe: false })); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); + MapPlaneNode.baseGeometry = MapPlaneNode.geometry; MapPlaneNode.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1.0, UnitsUtils.EARTH_PERIMETER); - class MapNodeHeightGeometry extends three.BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - const data = imageData.data; - for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - vertices[j + 1] = value; - } - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - if (calculateNormals) { - this.computeNormals(widthSegments, heightSegments); - } - } - computeNormals(widthSegments, heightSegments) { - const positionAttribute = this.getAttribute('position'); - if (positionAttribute !== undefined) { - let normalAttribute = this.getAttribute('normal'); - const normalLength = heightSegments * widthSegments; - for (let i = 0; i < normalLength; i++) { - normalAttribute.setXYZ(i, 0, 0, 0); - } - const pA = new three.Vector3(), pB = new three.Vector3(), pC = new three.Vector3(); - const nA = new three.Vector3(), nB = new three.Vector3(), nC = new three.Vector3(); - const cb = new three.Vector3(), ab = new three.Vector3(); - const indexLength = heightSegments * widthSegments * 6; - for (let i = 0; i < indexLength; i += 3) { - const vA = this.index.getX(i + 0); - const vB = this.index.getX(i + 1); - const vC = this.index.getX(i + 2); - pA.fromBufferAttribute(positionAttribute, vA); - pB.fromBufferAttribute(positionAttribute, vB); - pC.fromBufferAttribute(positionAttribute, vC); - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - nA.fromBufferAttribute(normalAttribute, vA); - nB.fromBufferAttribute(normalAttribute, vB); - nC.fromBufferAttribute(normalAttribute, vC); - nA.add(cb); - nB.add(cb); - nC.add(cb); - normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); - normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); - normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); - } - this.normalizeNormals(); - normalAttribute.needsUpdate = true; - } - } + class MapNodeHeightGeometry extends three.BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + const data = imageData.data; + for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + vertices[j + 1] = value; + } + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + if (calculateNormals) { + this.computeNormals(widthSegments, heightSegments); + } + } + computeNormals(widthSegments, heightSegments) { + const positionAttribute = this.getAttribute('position'); + if (positionAttribute !== undefined) { + let normalAttribute = this.getAttribute('normal'); + const normalLength = heightSegments * widthSegments; + for (let i = 0; i < normalLength; i++) { + normalAttribute.setXYZ(i, 0, 0, 0); + } + const pA = new three.Vector3(), pB = new three.Vector3(), pC = new three.Vector3(); + const nA = new three.Vector3(), nB = new three.Vector3(), nC = new three.Vector3(); + const cb = new three.Vector3(), ab = new three.Vector3(); + const indexLength = heightSegments * widthSegments * 6; + for (let i = 0; i < indexLength; i += 3) { + const vA = this.index.getX(i + 0); + const vB = this.index.getX(i + 1); + const vC = this.index.getX(i + 2); + pA.fromBufferAttribute(positionAttribute, vA); + pB.fromBufferAttribute(positionAttribute, vB); + pC.fromBufferAttribute(positionAttribute, vC); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + nA.fromBufferAttribute(normalAttribute, vA); + nB.fromBufferAttribute(normalAttribute, vB); + nC.fromBufferAttribute(normalAttribute, vC); + nA.add(cb); + nB.add(cb); + nC.add(cb); + normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); + normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); + normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); + } + this.normalizeNormals(); + normalAttribute.needsUpdate = true; + } + } } - class MapHeightNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new three.MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { - super(parentNode, mapView, location, level, x, y, geometry, material); - this.heightLoaded = false; - this.textureLoaded = false; - this.geometrySize = 16; - this.geometryNormals = false; - this.isMesh = true; - this.visible = false; - this.matrixAutoUpdate = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - yield this.loadHeightGeometry(); - this.nodeReady(); - }); - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.geometry = MapPlaneNode.baseGeometry; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); - const context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); - const imageData = context.getImageData(0, 0, canvas.width, canvas.height); - this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); - } - catch (e) { - if (this.disposed) { - return; - } - this.geometry = MapPlaneNode.baseGeometry; - } - this.heightLoaded = true; - }); - } - createChildNodes() { - const level = this.level + 1; - const Constructor = Object.getPrototypeOf(this).constructor; - const x = this.x * 2; - const y = this.y * 2; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapHeightNode.tileSize = 256; - MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); - MapHeightNode.baseGeometry = MapPlaneNode.geometry; + class MapHeightNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new three.MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { + super(parentNode, mapView, location, level, x, y, geometry, material); + this.heightLoaded = false; + this.textureLoaded = false; + this.geometrySize = 16; + this.geometryNormals = false; + this.isMesh = true; + this.visible = false; + this.matrixAutoUpdate = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + yield this.loadHeightGeometry(); + this.nodeReady(); + }); + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.geometry = MapPlaneNode.baseGeometry; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); + } + catch (e) { + if (this.disposed) { + return; + } + this.geometry = MapPlaneNode.baseGeometry; + } + this.heightLoaded = true; + }); + } + createChildNodes() { + const level = this.level + 1; + const Constructor = Object.getPrototypeOf(this).constructor; + const x = this.x * 2; + const y = this.y * 2; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapHeightNode.tileSize = 256; + MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + MapHeightNode.baseGeometry = MapPlaneNode.geometry; MapHeightNode.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); - class MapSphereNodeGeometry extends three.BufferGeometry { - constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { - super(); - const thetaEnd = thetaStart + thetaLength; - let index = 0; - const grid = []; - const vertex = new three.Vector3(); - const normal = new three.Vector3(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - for (let iy = 0; iy <= heightSegments; iy++) { - const verticesRow = []; - const v = iy / heightSegments; - for (let ix = 0; ix <= widthSegments; ix++) { - const u = ix / widthSegments; - vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertex.y = radius * Math.cos(thetaStart + v * thetaLength); - vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertices.push(vertex.x, vertex.y, vertex.z); - normal.set(vertex.x, vertex.y, vertex.z).normalize(); - normals.push(normal.x, normal.y, normal.z); - uvs.push(u, 1 - v); - verticesRow.push(index++); - } - grid.push(verticesRow); - } - for (let iy = 0; iy < heightSegments; iy++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = grid[iy][ix + 1]; - const b = grid[iy][ix]; - const c = grid[iy + 1][ix]; - const d = grid[iy + 1][ix + 1]; - if (iy !== 0 || thetaStart > 0) { - indices.push(a, b, d); - } - if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { - indices.push(b, c, d); - } - } - } - this.setIndex(indices); - this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); - } + class MapSphereNodeGeometry extends three.BufferGeometry { + constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { + super(); + const thetaEnd = thetaStart + thetaLength; + let index = 0; + const grid = []; + const vertex = new three.Vector3(); + const normal = new three.Vector3(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + for (let iy = 0; iy <= heightSegments; iy++) { + const verticesRow = []; + const v = iy / heightSegments; + for (let ix = 0; ix <= widthSegments; ix++) { + const u = ix / widthSegments; + vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertex.y = radius * Math.cos(thetaStart + v * thetaLength); + vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertices.push(vertex.x, vertex.y, vertex.z); + normal.set(vertex.x, vertex.y, vertex.z).normalize(); + normals.push(normal.x, normal.y, normal.z); + uvs.push(u, 1 - v); + verticesRow.push(index++); + } + grid.push(verticesRow); + } + for (let iy = 0; iy < heightSegments; iy++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = grid[iy][ix + 1]; + const b = grid[iy][ix]; + const c = grid[iy + 1][ix]; + const d = grid[iy + 1][ix + 1]; + if (iy !== 0 || thetaStart > 0) { + indices.push(a, b, d); + } + if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { + indices.push(b, c, d); + } + } + } + this.setIndex(indices); + this.setAttribute('position', new three.Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new three.Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new three.Float32BufferAttribute(uvs, 2)); + } } - class MapSphereNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(three.REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(three.REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new three.Vector4(...bounds); - const material = new three.ShaderMaterial({ - uniforms: { uTexture: { value: new three.Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); - this.applyScaleNode(); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - static createGeometry(zoom, x, y) { - const range = Math.pow(2, zoom); - const max = 40; - const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); - return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); - } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new three.TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(three.REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } - applyScaleNode() { - this.geometry.computeBoundingBox(); - const box = this.geometry.boundingBox.clone(); - const center = box.getCenter(new three.Vector3()); - const matrix = new three.Matrix4(); - matrix.compose(new three.Vector3(-center.x, -center.y, -center.z), new three.Quaternion(), new three.Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); - this.geometry.applyMatrix4(matrix); - this.position.copy(center); - this.updateMatrix(); - this.updateMatrixWorld(); - } - updateMatrix() { - this.matrix.setPosition(this.position); - this.matrixWorldNeedsUpdate = true; - } - updateMatrixWorld(force = false) { - if (this.matrixWorldNeedsUpdate || force) { - this.matrixWorld.copy(this.matrix); - this.matrixWorldNeedsUpdate = false; - } - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - this.add(node); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } - } - MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); - MapSphereNode.baseScale = new three.Vector3(1, 1, 1); + class MapSphereNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new three.MeshBasicMaterial({ wireframe: false })); + this.applyScaleNode(); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + static createGeometry(zoom, x, y) { + const range = Math.pow(2, zoom); + const max = 40; + const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; + return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); + } + applyScaleNode() { + this.geometry.computeBoundingBox(); + const box = this.geometry.boundingBox.clone(); + const center = box.getCenter(new three.Vector3()); + const matrix = new three.Matrix4(); + matrix.compose(new three.Vector3(-center.x, -center.y, -center.z), new three.Quaternion(), new three.Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); + this.geometry.applyMatrix4(matrix); + this.position.copy(center); + this.updateMatrix(); + this.updateMatrixWorld(); + } + updateMatrix() { + this.matrix.setPosition(this.position); + this.matrixWorldNeedsUpdate = true; + } + updateMatrixWorld(force = false) { + if (this.matrixWorldNeedsUpdate || force) { + this.matrixWorld.copy(this.matrix); + this.matrixWorldNeedsUpdate = false; + } + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + this.add(node); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } + } + MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); + MapSphereNode.baseScale = new three.Vector3(1, 1, 1); MapSphereNode.segments = 80; - class MapHeightNodeShader extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - const material = MapHeightNodeShader.prepareMaterial(new three.MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); - super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); - this.frustumCulled = false; - } - static prepareMaterial(material) { - material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; - material.onBeforeCompile = (shader) => { - for (const i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = + class MapHeightNodeShader extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + const material = MapHeightNodeShader.prepareMaterial(new three.MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); + super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); + this.frustumCulled = false; + } + static prepareMaterial(material) { + material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; + material.onBeforeCompile = (shader) => { + for (const i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform sampler2D heightMap; - ` + shader.vertexShader; + ` + shader.vertexShader; shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -858,393 +773,389 @@ // Vertex position based on height gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0); - `); - }; - return material; - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapHeightNodeShader.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.NearestFilter; - texture.minFilter = three.NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.error('Geo-Three: Failed to load node tile height data.', this); - this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; - } - this.material.needsUpdate = true; - this.heightLoaded = true; - }); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - this.geometry = MapPlaneNode.geometry; - super.raycast(raycaster, intersects); - this.geometry = MapHeightNodeShader.geometry; - } - } - dispose() { - super.dispose(); - if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { - this.material.userData.heightMap.value.dispose(); - } - } - } - MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); - MapHeightNodeShader.geometrySize = 256; - MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); - MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; + `); + }; + return material; + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapHeightNodeShader.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.NearestFilter; + texture.minFilter = three.NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.error('Geo-Three: Failed to load node tile height data.', this); + this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; + } + this.material.needsUpdate = true; + this.heightLoaded = true; + }); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + this.geometry = MapPlaneNode.geometry; + super.raycast(raycaster, intersects); + this.geometry = MapHeightNodeShader.geometry; + } + } + dispose() { + super.dispose(); + if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { + this.material.userData.heightMap.value.dispose(); + } + } + } + MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); + MapHeightNodeShader.geometrySize = 256; + MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); + MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; MapHeightNodeShader.baseScale = new three.Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); - class LODRaycast { - constructor() { - this.subdivisionRays = 1; - this.thresholdUp = 0.6; - this.thresholdDown = 0.15; - this.raycaster = new three.Raycaster(); - this.mouse = new three.Vector2(); - this.powerDistance = false; - this.scaleDistance = true; - } - updateLOD(view, camera, renderer, scene) { - const intersects = []; - for (let t = 0; t < this.subdivisionRays; t++) { - this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); - this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } - } - for (let i = 0; i < intersects.length; i++) { - const node = intersects[i].object; - let distance = intersects[i].distance; - if (this.powerDistance) { - distance = Math.pow(distance * 2, node.level); - } - if (this.scaleDistance) { - const matrix = node.matrixWorld.elements; - const vector = new three.Vector3(matrix[0], matrix[1], matrix[2]); - distance = vector.length() / distance; - } - if (distance > this.thresholdUp) { - node.subdivide(); - } - else if (distance < this.thresholdDown && node.parentNode) { - node.parentNode.simplify(); - } - } - } + class LODRaycast { + constructor() { + this.subdivisionRays = 1; + this.thresholdUp = 0.6; + this.thresholdDown = 0.15; + this.raycaster = new three.Raycaster(); + this.mouse = new three.Vector2(); + this.powerDistance = false; + this.scaleDistance = true; + } + updateLOD(view, camera, renderer, scene) { + const intersects = []; + for (let t = 0; t < this.subdivisionRays; t++) { + this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); + this.raycaster.setFromCamera(this.mouse, camera); + this.raycaster.intersectObjects(view.children, true, intersects); + } + for (let i = 0; i < intersects.length; i++) { + const node = intersects[i].object; + let distance = intersects[i].distance; + if (this.powerDistance) { + distance = Math.pow(distance * 2, node.level); + } + if (this.scaleDistance) { + const matrix = node.matrixWorld.elements; + const vector = new three.Vector3(matrix[0], matrix[1], matrix[2]); + distance = vector.length() / distance; + } + if (distance > this.thresholdUp) { + node.subdivide(); + } + else if (distance < this.thresholdDown && node.parentNode) { + node.parentNode.simplify(); + } + } + } } - class Martini { - constructor(gridSize = 257) { - this.gridSize = gridSize; - const tileSize = gridSize - 1; - if (tileSize & tileSize - 1) { - throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); - } - this.numTriangles = tileSize * tileSize * 2 - 2; - this.numParentTriangles = this.numTriangles - tileSize * tileSize; - this.indices = new Uint32Array(this.gridSize * this.gridSize); - this.coords = new Uint16Array(this.numTriangles * 4); - for (let i = 0; i < this.numTriangles; i++) { - let id = i + 2; - let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; - if (id & 1) { - bx = by = cx = tileSize; - } - else { - ax = ay = cy = tileSize; - } - while ((id >>= 1) > 1) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (id & 1) { - bx = ax; - by = ay; - ax = cx; - ay = cy; - } - else { - ax = bx; - ay = by; - bx = cx; - by = cy; - } - cx = mx; - cy = my; - } - const k = i * 4; - this.coords[k + 0] = ax; - this.coords[k + 1] = ay; - this.coords[k + 2] = bx; - this.coords[k + 3] = by; - } - } - createTile(terrain) { - return new Tile(terrain, this); - } - } - class Tile { - constructor(terrain, martini) { - const size = martini.gridSize; - if (terrain.length !== size * size) { - throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); - } - this.terrain = terrain; - this.martini = martini; - this.errors = new Float32Array(terrain.length); - this.update(); - } - update() { - const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; - const { terrain, errors } = this; - for (let i = numTriangles - 1; i >= 0; i--) { - const k = i * 4; - const ax = coords[k + 0]; - const ay = coords[k + 1]; - const bx = coords[k + 2]; - const by = coords[k + 3]; - const mx = ax + bx >> 1; - const my = ay + by >> 1; - const cx = mx + my - ay; - const cy = my + ax - mx; - const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; - const middleIndex = my * size + mx; - const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); - errors[middleIndex] = Math.max(errors[middleIndex], middleError); - if (i < numParentTriangles) { - const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); - const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); - errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); - } - } - } - getMesh(maxError = 0, withSkirts = false) { - const { gridSize: size, indices } = this.martini; - const { errors } = this; - let numVertices = 0; - let numTriangles = 0; - const max = size - 1; - let aIndex, bIndex, cIndex = 0; - const leftSkirtIndices = []; - const rightSkirtIndices = []; - const bottomSkirtIndices = []; - const topSkirtIndices = []; - indices.fill(0); - function countElements(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - countElements(cx, cy, ax, ay, mx, my); - countElements(bx, by, cx, cy, mx, my); - } - else { - aIndex = ay * size + ax; - bIndex = by * size + bx; - cIndex = cy * size + cx; - if (indices[aIndex] === 0) { - if (withSkirts) { - if (ax === 0) { - leftSkirtIndices.push(numVertices); - } - else if (ax === max) { - rightSkirtIndices.push(numVertices); - } - if (ay === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (ay === max) { - topSkirtIndices.push(numVertices); - } - } - indices[aIndex] = ++numVertices; - } - if (indices[bIndex] === 0) { - if (withSkirts) { - if (bx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (bx === max) { - rightSkirtIndices.push(numVertices); - } - if (by === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (by === max) { - topSkirtIndices.push(numVertices); - } - } - indices[bIndex] = ++numVertices; - } - if (indices[cIndex] === 0) { - if (withSkirts) { - if (cx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (cx === max) { - rightSkirtIndices.push(numVertices); - } - if (cy === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (cy === max) { - topSkirtIndices.push(numVertices); - } - } - indices[cIndex] = ++numVertices; - } - numTriangles++; - } - } - countElements(0, 0, max, max, max, 0); - countElements(max, max, 0, 0, 0, max); - let numTotalVertices = numVertices * 2; - let numTotalTriangles = numTriangles * 3; - if (withSkirts) { - numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; - numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; - } - const vertices = new Uint16Array(numTotalVertices); - const triangles = new Uint32Array(numTotalTriangles); - let triIndex = 0; - function processTriangle(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - processTriangle(cx, cy, ax, ay, mx, my); - processTriangle(bx, by, cx, cy, mx, my); - } - else { - const a = indices[ay * size + ax] - 1; - const b = indices[by * size + bx] - 1; - const c = indices[cy * size + cx] - 1; - vertices[2 * a] = ax; - vertices[2 * a + 1] = ay; - vertices[2 * b] = bx; - vertices[2 * b + 1] = by; - vertices[2 * c] = cx; - vertices[2 * c + 1] = cy; - triangles[triIndex++] = a; - triangles[triIndex++] = b; - triangles[triIndex++] = c; - } - } - processTriangle(0, 0, max, max, max, 0); - processTriangle(max, max, 0, 0, 0, max); - if (withSkirts) { - leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); - rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); - bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); - topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); - let skirtIndex = numVertices * 2; - function constructSkirt(skirt) { - const skirtLength = skirt.length; - for (let i = 0; i < skirtLength - 1; i++) { - const currIndex = skirt[i]; - const nextIndex = skirt[i + 1]; - const currentSkirt = skirtIndex / 2; - const nextSkirt = (skirtIndex + 2) / 2; - vertices[skirtIndex++] = vertices[2 * currIndex]; - vertices[skirtIndex++] = vertices[2 * currIndex + 1]; - triangles[triIndex++] = currIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextSkirt; - triangles[triIndex++] = nextIndex; - } - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; - } - constructSkirt(leftSkirtIndices); - constructSkirt(rightSkirtIndices); - constructSkirt(bottomSkirtIndices); - constructSkirt(topSkirtIndices); - } - return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; - } + class Martini { + constructor(gridSize = 257) { + this.gridSize = gridSize; + const tileSize = gridSize - 1; + if (tileSize & tileSize - 1) { + throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); + } + this.numTriangles = tileSize * tileSize * 2 - 2; + this.numParentTriangles = this.numTriangles - tileSize * tileSize; + this.indices = new Uint32Array(this.gridSize * this.gridSize); + this.coords = new Uint16Array(this.numTriangles * 4); + for (let i = 0; i < this.numTriangles; i++) { + let id = i + 2; + let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; + if (id & 1) { + bx = by = cx = tileSize; + } + else { + ax = ay = cy = tileSize; + } + while ((id >>= 1) > 1) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (id & 1) { + bx = ax; + by = ay; + ax = cx; + ay = cy; + } + else { + ax = bx; + ay = by; + bx = cx; + by = cy; + } + cx = mx; + cy = my; + } + const k = i * 4; + this.coords[k + 0] = ax; + this.coords[k + 1] = ay; + this.coords[k + 2] = bx; + this.coords[k + 3] = by; + } + } + createTile(terrain) { + return new Tile(terrain, this); + } + } + class Tile { + constructor(terrain, martini) { + const size = martini.gridSize; + if (terrain.length !== size * size) { + throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); + } + this.terrain = terrain; + this.martini = martini; + this.errors = new Float32Array(terrain.length); + this.update(); + } + update() { + const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; + const { terrain, errors } = this; + for (let i = numTriangles - 1; i >= 0; i--) { + const k = i * 4; + const ax = coords[k + 0]; + const ay = coords[k + 1]; + const bx = coords[k + 2]; + const by = coords[k + 3]; + const mx = ax + bx >> 1; + const my = ay + by >> 1; + const cx = mx + my - ay; + const cy = my + ax - mx; + const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; + const middleIndex = my * size + mx; + const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); + errors[middleIndex] = Math.max(errors[middleIndex], middleError); + if (i < numParentTriangles) { + const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); + const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); + errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); + } + } + } + getMesh(maxError = 0, withSkirts = false) { + const { gridSize: size, indices } = this.martini; + const { errors } = this; + let numVertices = 0; + let numTriangles = 0; + const max = size - 1; + let aIndex, bIndex, cIndex = 0; + const leftSkirtIndices = []; + const rightSkirtIndices = []; + const bottomSkirtIndices = []; + const topSkirtIndices = []; + indices.fill(0); + function countElements(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + countElements(cx, cy, ax, ay, mx, my); + countElements(bx, by, cx, cy, mx, my); + } + else { + aIndex = ay * size + ax; + bIndex = by * size + bx; + cIndex = cy * size + cx; + if (indices[aIndex] === 0) { + if (withSkirts) { + if (ax === 0) { + leftSkirtIndices.push(numVertices); + } + else if (ax === max) { + rightSkirtIndices.push(numVertices); + } + if (ay === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (ay === max) { + topSkirtIndices.push(numVertices); + } + } + indices[aIndex] = ++numVertices; + } + if (indices[bIndex] === 0) { + if (withSkirts) { + if (bx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (bx === max) { + rightSkirtIndices.push(numVertices); + } + if (by === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (by === max) { + topSkirtIndices.push(numVertices); + } + } + indices[bIndex] = ++numVertices; + } + if (indices[cIndex] === 0) { + if (withSkirts) { + if (cx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (cx === max) { + rightSkirtIndices.push(numVertices); + } + if (cy === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (cy === max) { + topSkirtIndices.push(numVertices); + } + } + indices[cIndex] = ++numVertices; + } + numTriangles++; + } + } + countElements(0, 0, max, max, max, 0); + countElements(max, max, 0, 0, 0, max); + let numTotalVertices = numVertices * 2; + let numTotalTriangles = numTriangles * 3; + if (withSkirts) { + numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; + numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; + } + const vertices = new Uint16Array(numTotalVertices); + const triangles = new Uint32Array(numTotalTriangles); + let triIndex = 0; + function processTriangle(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + processTriangle(cx, cy, ax, ay, mx, my); + processTriangle(bx, by, cx, cy, mx, my); + } + else { + const a = indices[ay * size + ax] - 1; + const b = indices[by * size + bx] - 1; + const c = indices[cy * size + cx] - 1; + vertices[2 * a] = ax; + vertices[2 * a + 1] = ay; + vertices[2 * b] = bx; + vertices[2 * b + 1] = by; + vertices[2 * c] = cx; + vertices[2 * c + 1] = cy; + triangles[triIndex++] = a; + triangles[triIndex++] = b; + triangles[triIndex++] = c; + } + } + processTriangle(0, 0, max, max, max, 0); + processTriangle(max, max, 0, 0, 0, max); + if (withSkirts) { + leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); + rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); + bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); + topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); + let skirtIndex = numVertices * 2; + function constructSkirt(skirt) { + const skirtLength = skirt.length; + for (let i = 0; i < skirtLength - 1; i++) { + const currIndex = skirt[i]; + const nextIndex = skirt[i + 1]; + const currentSkirt = skirtIndex / 2; + const nextSkirt = (skirtIndex + 2) / 2; + vertices[skirtIndex++] = vertices[2 * currIndex]; + vertices[skirtIndex++] = vertices[2 * currIndex + 1]; + triangles[triIndex++] = currIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextSkirt; + triangles[triIndex++] = nextIndex; + } + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; + } + constructSkirt(leftSkirtIndices); + constructSkirt(rightSkirtIndices); + constructSkirt(bottomSkirtIndices); + constructSkirt(topSkirtIndices); + } + return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; + } } - class MapMartiniHeightNode extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { - super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new three.MeshPhongMaterial({ - map: MapMartiniHeightNode.emptyTexture, - color: 0xFFFFFF, - side: three.DoubleSide - }), level, exageration)); - this.elevationDecoder = { - rScaler: 256, - gScaler: 1, - bScaler: 1 / 256, - offset: -32768 - }; - this.exageration = 1.0; - this.meshMaxError = 10; - if (elevationDecoder) { - this.elevationDecoder = elevationDecoder; - } - this.meshMaxError = meshMaxError; - this.exageration = exageration; - this.frustumCulled = false; - } - static prepareMaterial(material, level, exageration = 1.0) { - material.userData = { - heightMap: { value: MapMartiniHeightNode.emptyTexture }, - drawNormals: { value: 0 }, - drawBlack: { value: 0 }, - zoomlevel: { value: level }, - computeNormals: { value: 1 }, - drawTexture: { value: 1 } - }; - material.onBeforeCompile = (shader) => { - for (let i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = + class MapMartiniHeightNode extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { + super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new three.MeshPhongMaterial({ + map: MapMartiniHeightNode.emptyTexture, + color: 0xFFFFFF, + side: three.DoubleSide + }), level, exageration)); + this.elevationDecoder = { + rScaler: 256, + gScaler: 1, + bScaler: 1 / 256, + offset: -32768 + }; + this.exageration = 1.0; + this.meshMaxError = 10; + if (elevationDecoder) { + this.elevationDecoder = elevationDecoder; + } + this.meshMaxError = meshMaxError; + this.exageration = exageration; + this.frustumCulled = false; + } + static prepareMaterial(material, level, exageration = 1.0) { + material.userData = { + heightMap: { value: MapMartiniHeightNode.emptyTexture }, + drawNormals: { value: 0 }, + drawBlack: { value: 0 }, + zoomlevel: { value: level }, + computeNormals: { value: 1 }, + drawTexture: { value: 1 } + }; + material.onBeforeCompile = (shader) => { + for (let i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform bool computeNormals; uniform float zoomlevel; uniform sampler2D heightMap; - ` + shader.vertexShader; - shader.fragmentShader = + ` + shader.vertexShader; + shader.fragmentShader = ` uniform bool drawNormals; uniform bool drawTexture; uniform bool drawBlack; - ` + shader.fragmentShader; + ` + shader.fragmentShader; shader.fragmentShader = shader.fragmentShader.replace('#include ', ` if(drawBlack) { gl_FragColor = vec4( 0.0,0.0,0.0, 1.0 ); @@ -1252,7 +1163,7 @@ gl_FragColor = vec4( ( 0.5 * vNormal + 0.5 ), 1.0 ); } else if (!drawTexture) { gl_FragColor = vec4( 0.0,0.0,0.0, 0.0 ); - }`); + }`); shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -1295,710 +1206,710 @@ v2.z = (e+ h + i + f) / 4.0; vNormal = (normalize(cross(v2 - v0, v1 - v0))).rbg; } - `); - }; - return material; - } - static getTerrain(imageData, tileSize, elevation) { - const { rScaler, bScaler, gScaler, offset } = elevation; - const gridSize = tileSize + 1; - const terrain = new Float32Array(gridSize * gridSize); - for (let i = 0, y = 0; y < tileSize; y++) { - for (let x = 0; x < tileSize; x++, i++) { - const k = i * 4; - const r = imageData[k + 0]; - const g = imageData[k + 1]; - const b = imageData[k + 2]; - terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; - } - } - for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { - terrain[i] = terrain[i - gridSize]; - } - for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { - terrain[i] = terrain[i - 1]; - } - return terrain; - } - static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { - const gridSize = tileSize + 1; - const numOfVerticies = vertices.length / 2; - const positions = new Float32Array(numOfVerticies * 3); - const texCoords = new Float32Array(numOfVerticies * 2); - const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; - const xScale = (maxX - minX) / tileSize; - const yScale = (maxY - minY) / tileSize; - for (let i = 0; i < numOfVerticies; i++) { - const x = vertices[i * 2]; - const y = vertices[i * 2 + 1]; - const pixelIdx = y * gridSize + x; - positions[3 * i + 0] = x * xScale + minX; - positions[3 * i + 1] = -terrain[pixelIdx] * exageration; - positions[3 * i + 2] = -y * yScale + maxY; - texCoords[2 * i + 0] = x / tileSize; - texCoords[2 * i + 1] = y / tileSize; - } - return { - position: { value: positions, size: 3 }, - uv: { value: texCoords, size: 2 } - }; - } - processHeight(image) { - return __awaiter(this, void 0, void 0, function* () { - const tileSize = image.width; - const gridSize = tileSize + 1; - var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); - var context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); - const martini = new Martini(gridSize); - const tile = martini.createTile(terrain); - const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); - const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); - this.geometry = new three.BufferGeometry(); - this.geometry.setIndex(new three.Uint32BufferAttribute(triangles, 1)); - this.geometry.setAttribute('position', new three.Float32BufferAttribute(attributes.position.value, attributes.position.size)); - this.geometry.setAttribute('uv', new three.Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); - this.geometry.rotateX(Math.PI); - var texture = new three.Texture(image); - texture.generateMipmaps = false; - texture.format = three.RGBAFormat; - texture.magFilter = three.NearestFilter; - texture.minFilter = three.NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - this.material.map = texture; - this.material.needsUpdate = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - this.processHeight(image); - this.heightLoaded = true; - this.nodeReady(); - }); - } - } - MapMartiniHeightNode.geometrySize = 16; - MapMartiniHeightNode.emptyTexture = new three.Texture(); - MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + `); + }; + return material; + } + static getTerrain(imageData, tileSize, elevation) { + const { rScaler, bScaler, gScaler, offset } = elevation; + const gridSize = tileSize + 1; + const terrain = new Float32Array(gridSize * gridSize); + for (let i = 0, y = 0; y < tileSize; y++) { + for (let x = 0; x < tileSize; x++, i++) { + const k = i * 4; + const r = imageData[k + 0]; + const g = imageData[k + 1]; + const b = imageData[k + 2]; + terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; + } + } + for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { + terrain[i] = terrain[i - gridSize]; + } + for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { + terrain[i] = terrain[i - 1]; + } + return terrain; + } + static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { + const gridSize = tileSize + 1; + const numOfVerticies = vertices.length / 2; + const positions = new Float32Array(numOfVerticies * 3); + const texCoords = new Float32Array(numOfVerticies * 2); + const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; + const xScale = (maxX - minX) / tileSize; + const yScale = (maxY - minY) / tileSize; + for (let i = 0; i < numOfVerticies; i++) { + const x = vertices[i * 2]; + const y = vertices[i * 2 + 1]; + const pixelIdx = y * gridSize + x; + positions[3 * i + 0] = x * xScale + minX; + positions[3 * i + 1] = -terrain[pixelIdx] * exageration; + positions[3 * i + 2] = -y * yScale + maxY; + texCoords[2 * i + 0] = x / tileSize; + texCoords[2 * i + 1] = y / tileSize; + } + return { + position: { value: positions, size: 3 }, + uv: { value: texCoords, size: 2 } + }; + } + processHeight(image) { + return __awaiter(this, void 0, void 0, function* () { + const tileSize = image.width; + const gridSize = tileSize + 1; + var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); + var context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); + var imageData = context.getImageData(0, 0, canvas.width, canvas.height); + var data = imageData.data; + const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); + const martini = new Martini(gridSize); + const tile = martini.createTile(terrain); + const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); + const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); + this.geometry = new three.BufferGeometry(); + this.geometry.setIndex(new three.Uint32BufferAttribute(triangles, 1)); + this.geometry.setAttribute('position', new three.Float32BufferAttribute(attributes.position.value, attributes.position.size)); + this.geometry.setAttribute('uv', new three.Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); + this.geometry.rotateX(Math.PI); + var texture = new three.Texture(image); + texture.generateMipmaps = false; + texture.format = three.RGBAFormat; + texture.magFilter = three.NearestFilter; + texture.minFilter = three.NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + this.material.map = texture; + this.material.needsUpdate = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + this.processHeight(image); + this.heightLoaded = true; + this.nodeReady(); + }); + } + } + MapMartiniHeightNode.geometrySize = 16; + MapMartiniHeightNode.emptyTexture = new three.Texture(); + MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); MapMartiniHeightNode.tileSize = 256; - class MapView extends three.Mesh { - constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); - this.lod = null; - this.provider = null; - this.heightProvider = null; - this.root = null; - this.cacheTiles = false; - this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { - this.lod.updateLOD(this, camera, renderer, scene); - }; - this.lod = new LODRaycast(); - this.provider = provider; - this.heightProvider = heightProvider; - this.setRoot(root); - this.preSubdivide(); - } - setRoot(root) { - if (typeof root === 'number') { - if (!MapView.mapModes.has(root)) { - throw new Error('Map mode ' + root + ' does is not registered.'); - } - const rootConstructor = MapView.mapModes.get(root); - root = new rootConstructor(null, this); - } - if (this.root !== null) { - this.remove(this.root); - this.root = null; - } - this.root = root; - if (this.root !== null) { - this.geometry = this.root.constructor.baseGeometry; - this.scale.copy(this.root.constructor.baseScale); - this.root.mapView = this; - this.add(this.root); - this.root.initialize(); - } - } - preSubdivide() { - var _a, _b; - function subdivide(node, depth) { - if (depth <= 0) { - return; - } - node.subdivide(); - for (let i = 0; i < node.children.length; i++) { - if (node.children[i] instanceof MapNode) { - const child = node.children[i]; - subdivide(child, depth - 1); - } - } - } - const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - if (minZoom > 0) { - subdivide(this.root, minZoom); - } - } - setProvider(provider) { - if (provider !== this.provider) { - this.provider = provider; - this.clear(); - } - } - setHeightProvider(heightProvider) { - if (heightProvider !== this.heightProvider) { - this.heightProvider = heightProvider; - this.clear(); - } - } - clear() { - this.traverse(function (children) { - if (children.childrenCache) { - children.childrenCache = null; - } - if (children.initialize) { - children.initialize(); - } - }); - return this; - } - minZoom() { - var _a, _b; - return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - } - maxZoom() { - var _a, _b; - return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); - } - getMetaData() { - this.provider.getMetaData(); - } - raycast(raycaster, intersects) { - return false; - } - } - MapView.PLANAR = 200; - MapView.SPHERICAL = 201; - MapView.HEIGHT = 202; - MapView.HEIGHT_SHADER = 203; - MapView.MARTINI = 204; - MapView.mapModes = new Map([ - [MapView.PLANAR, MapPlaneNode], - [MapView.SPHERICAL, MapSphereNode], - [MapView.HEIGHT, MapHeightNode], - [MapView.HEIGHT_SHADER, MapHeightNodeShader], - [MapView.MARTINI, MapMartiniHeightNode] + class MapView extends three.Mesh { + constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { + super(undefined, new three.MeshBasicMaterial({ transparent: true, opacity: 0.0 })); + this.lod = null; + this.provider = null; + this.heightProvider = null; + this.root = null; + this.cacheTiles = false; + this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { + this.lod.updateLOD(this, camera, renderer, scene); + }; + this.lod = new LODRaycast(); + this.provider = provider; + this.heightProvider = heightProvider; + this.setRoot(root); + this.preSubdivide(); + } + setRoot(root) { + if (typeof root === 'number') { + if (!MapView.mapModes.has(root)) { + throw new Error('Map mode ' + root + ' does is not registered.'); + } + const rootConstructor = MapView.mapModes.get(root); + root = new rootConstructor(null, this); + } + if (this.root !== null) { + this.remove(this.root); + this.root = null; + } + this.root = root; + if (this.root !== null) { + this.geometry = this.root.constructor.baseGeometry; + this.scale.copy(this.root.constructor.baseScale); + this.root.mapView = this; + this.add(this.root); + this.root.initialize(); + } + } + preSubdivide() { + var _a, _b; + function subdivide(node, depth) { + if (depth <= 0) { + return; + } + node.subdivide(); + for (let i = 0; i < node.children.length; i++) { + if (node.children[i] instanceof MapNode) { + const child = node.children[i]; + subdivide(child, depth - 1); + } + } + } + const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + if (minZoom > 0) { + subdivide(this.root, minZoom); + } + } + setProvider(provider) { + if (provider !== this.provider) { + this.provider = provider; + this.clear(); + } + } + setHeightProvider(heightProvider) { + if (heightProvider !== this.heightProvider) { + this.heightProvider = heightProvider; + this.clear(); + } + } + clear() { + this.traverse(function (children) { + if (children.childrenCache) { + children.childrenCache = null; + } + if (children.initialize) { + children.initialize(); + } + }); + return this; + } + minZoom() { + var _a, _b; + return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + } + maxZoom() { + var _a, _b; + return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); + } + getMetaData() { + this.provider.getMetaData(); + } + raycast(raycaster, intersects) { + return false; + } + } + MapView.PLANAR = 200; + MapView.SPHERICAL = 201; + MapView.HEIGHT = 202; + MapView.HEIGHT_SHADER = 203; + MapView.MARTINI = 204; + MapView.mapModes = new Map([ + [MapView.PLANAR, MapPlaneNode], + [MapView.SPHERICAL, MapSphereNode], + [MapView.HEIGHT, MapHeightNode], + [MapView.HEIGHT_SHADER, MapHeightNodeShader], + [MapView.MARTINI, MapMartiniHeightNode] ]); - const pov$1 = new three.Vector3(); - const position$1 = new three.Vector3(); - class LODRadial { - constructor(subdivideDistance = 50, simplifyDistance = 300) { - this.subdivideDistance = subdivideDistance; - this.simplifyDistance = simplifyDistance; - } - updateLOD(view, camera, renderer, scene) { - camera.getWorldPosition(pov$1); - view.children[0].traverse((node) => { - node.getWorldPosition(position$1); - let distance = pov$1.distanceTo(position$1); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - if (distance < this.subdivideDistance) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } + const pov$1 = new three.Vector3(); + const position$1 = new three.Vector3(); + class LODRadial { + constructor(subdivideDistance = 50, simplifyDistance = 300) { + this.subdivideDistance = subdivideDistance; + this.simplifyDistance = simplifyDistance; + } + updateLOD(view, camera, renderer, scene) { + camera.getWorldPosition(pov$1); + view.children[0].traverse((node) => { + node.getWorldPosition(position$1); + let distance = pov$1.distanceTo(position$1); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + if (distance < this.subdivideDistance) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } - const projection = new three.Matrix4(); - const pov = new three.Vector3(); - const frustum = new three.Frustum(); - const position = new three.Vector3(); - class LODFrustum extends LODRadial { - constructor(subdivideDistance = 120, simplifyDistance = 400) { - super(subdivideDistance, simplifyDistance); - this.testCenter = true; - this.pointOnly = false; - } - updateLOD(view, camera, renderer, scene) { - projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromProjectionMatrix(projection); - camera.getWorldPosition(pov); - view.children[0].traverse((node) => { - node.getWorldPosition(position); - let distance = pov.distanceTo(position); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); - if (distance < this.subdivideDistance && inFrustum) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } + const projection = new three.Matrix4(); + const pov = new three.Vector3(); + const frustum = new three.Frustum(); + const position = new three.Vector3(); + class LODFrustum extends LODRadial { + constructor(subdivideDistance = 120, simplifyDistance = 400) { + super(subdivideDistance, simplifyDistance); + this.testCenter = true; + this.pointOnly = false; + } + updateLOD(view, camera, renderer, scene) { + projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projection); + camera.getWorldPosition(pov); + view.children[0].traverse((node) => { + node.getWorldPosition(position); + let distance = pov.distanceTo(position); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); + if (distance < this.subdivideDistance && inFrustum) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } - class XHRUtils { - static get(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static getRaw(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static request(url, type, header, body, onLoad, onError, onProgress) { - function parseResponse(response) { - try { - return JSON.parse(response); - } - catch (e) { - return response; - } - } - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open(type, url, true); - if (header !== null && header !== undefined) { - for (const i in header) { - xhr.setRequestHeader(i, header[i]); - } - } - if (onLoad !== undefined) { - xhr.onload = function (event) { - onLoad(parseResponse(xhr.response), xhr); - }; - } - if (onError !== undefined) { - xhr.onerror = onError; - } - if (onProgress !== undefined) { - xhr.onprogress = onProgress; - } - xhr.send(body !== undefined ? body : null); - return xhr; - } + class XHRUtils { + static get(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static getRaw(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static request(url, type, header, body, onLoad, onError, onProgress) { + function parseResponse(response) { + try { + return JSON.parse(response); + } + catch (e) { + return response; + } + } + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open(type, url, true); + if (header !== null && header !== undefined) { + for (const i in header) { + xhr.setRequestHeader(i, header[i]); + } + } + if (onLoad !== undefined) { + xhr.onload = function (event) { + onLoad(parseResponse(xhr.response), xhr); + }; + } + if (onError !== undefined) { + xhr.onerror = onError; + } + if (onProgress !== undefined) { + xhr.onprogress = onProgress; + } + xhr.send(body !== undefined ? body : null); + return xhr; + } } - class BingMapsProvider extends MapProvider { - constructor(apiKey = '', type = BingMapsProvider.AERIAL) { - super(); - this.maxZoom = 19; - this.minZoom = 1; - this.format = 'jpeg'; - this.mapSize = 512; - this.subdomain = 't1'; - this.meta = null; - this.apiKey = apiKey; - this.type = type; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; - const data = yield XHRUtils.get(address); - this.meta = JSON.parse(data); - }); - } - static quadKey(zoom, x, y) { - let quad = ''; - for (let i = zoom; i > 0; i--) { - const mask = 1 << i - 1; - let cell = 0; - if ((x & mask) !== 0) { - cell++; - } - if ((y & mask) !== 0) { - cell += 2; - } - quad += cell; - } - return quad; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; - }); - } - } - BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; - BingMapsProvider.AERIAL = 'a'; - BingMapsProvider.ROAD = 'r'; - BingMapsProvider.AERIAL_LABELS = 'h'; - BingMapsProvider.OBLIQUE = 'o'; + class BingMapsProvider extends MapProvider { + constructor(apiKey = '', type = BingMapsProvider.AERIAL) { + super(); + this.maxZoom = 19; + this.minZoom = 1; + this.format = 'jpeg'; + this.mapSize = 512; + this.subdomain = 't1'; + this.meta = null; + this.apiKey = apiKey; + this.type = type; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; + const data = yield XHRUtils.get(address); + this.meta = JSON.parse(data); + }); + } + static quadKey(zoom, x, y) { + let quad = ''; + for (let i = zoom; i > 0; i--) { + const mask = 1 << i - 1; + let cell = 0; + if ((x & mask) !== 0) { + cell++; + } + if ((y & mask) !== 0) { + cell += 2; + } + quad += cell; + } + return quad; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; + }); + } + } + BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; + BingMapsProvider.AERIAL = 'a'; + BingMapsProvider.ROAD = 'r'; + BingMapsProvider.AERIAL_LABELS = 'h'; + BingMapsProvider.OBLIQUE = 'o'; BingMapsProvider.OBLIQUE_LABELS = 'b'; - class GoogleMapsProvider extends MapProvider { - constructor(apiToken) { - super(); - this.sessionToken = null; - this.orientation = 0; - this.format = 'png'; - this.mapType = 'roadmap'; - this.overlay = false; - this.apiToken = apiToken !== undefined ? apiToken : ''; - this.createSession(); - } - createSession() { - const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; - const data = JSON.stringify({ - mapType: this.mapType, - language: 'en-EN', - region: 'en', - layerTypes: ['layerRoadmap', 'layerStreetview'], - overlay: this.overlay, - scale: 'scaleFactor1x' - }); - XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { - this.sessionToken = response.session; - }, function (xhr) { - throw new Error('Unable to create a google maps session.'); - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; - }); - } + class GoogleMapsProvider extends MapProvider { + constructor(apiToken) { + super(); + this.sessionToken = null; + this.orientation = 0; + this.format = 'png'; + this.mapType = 'roadmap'; + this.overlay = false; + this.apiToken = apiToken !== undefined ? apiToken : ''; + this.createSession(); + } + createSession() { + const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; + const data = JSON.stringify({ + mapType: this.mapType, + language: 'en-EN', + region: 'en', + layerTypes: ['layerRoadmap', 'layerStreetview'], + overlay: this.overlay, + scale: 'scaleFactor1x' + }); + XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + this.sessionToken = response.session; + }, function (xhr) { + throw new Error('Unable to create a google maps session.'); + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + }); + } } - class HereMapsProvider extends MapProvider { - constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { - super(); - this.appId = appId; - this.appCode = appCode; - this.style = style; - this.scheme = scheme; - this.format = format; - this.size = size; - this.version = 'newest'; - this.server = 1; - } - nextServer() { - this.server = this.server % 4 === 0 ? 1 : this.server + 1; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } - fetchTile(zoom, x, y) { - this.nextServer(); - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + - this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + - this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; - }); - } - } + class HereMapsProvider extends MapProvider { + constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { + super(); + this.appId = appId; + this.appCode = appCode; + this.style = style; + this.scheme = scheme; + this.format = format; + this.size = size; + this.version = 'newest'; + this.server = 1; + } + nextServer() { + this.server = this.server % 4 === 0 ? 1 : this.server + 1; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } + fetchTile(zoom, x, y) { + this.nextServer(); + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + + this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + + this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; + }); + } + } HereMapsProvider.PATH = '/maptile/2.1/'; - class MapBoxProvider extends MapProvider { - constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { - super(); - this.apiToken = apiToken; - this.format = format; - this.useHDPI = useHDPI; - this.mode = mode; - this.mapId = id; - this.style = id; - this.version = version; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - if (this.mode === MapBoxProvider.STYLE) { - image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; - } - else { - image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; - } - }); - } - } - MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; - MapBoxProvider.STYLE = 100; + class MapBoxProvider extends MapProvider { + constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { + super(); + this.apiToken = apiToken; + this.format = format; + this.useHDPI = useHDPI; + this.mode = mode; + this.mapId = id; + this.style = id; + this.version = version; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + if (this.mode === MapBoxProvider.STYLE) { + image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; + } + else { + image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; + } + }); + } + } + MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; + MapBoxProvider.STYLE = 100; MapBoxProvider.MAP_ID = 101; - class MapTilerProvider extends MapProvider { - constructor(apiKey, category, style, format) { - super(); - this.apiKey = apiKey !== undefined ? apiKey : ''; - this.format = format !== undefined ? format : 'png'; - this.category = category !== undefined ? category : 'maps'; - this.style = style !== undefined ? style : 'satellite'; - this.resolution = 512; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; - }); - } + class MapTilerProvider extends MapProvider { + constructor(apiKey, category, style, format) { + super(); + this.apiKey = apiKey !== undefined ? apiKey : ''; + this.format = format !== undefined ? format : 'png'; + this.category = category !== undefined ? category : 'maps'; + this.style = style !== undefined ? style : 'satellite'; + this.resolution = 512; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; + }); + } } - class OpenMapTilesProvider extends MapProvider { - constructor(address, format = 'png', theme = 'klokantech-basic') { - super(); - this.address = address; - this.format = format; - this.theme = theme; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = this.address + 'styles/' + this.theme + '.json'; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.format = meta.format; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } + class OpenMapTilesProvider extends MapProvider { + constructor(address, format = 'png', theme = 'klokantech-basic') { + super(); + this.address = address; + this.format = format; + this.theme = theme; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = this.address + 'styles/' + this.theme + '.json'; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.format = meta.format; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } - class DebugProvider extends MapProvider { - constructor() { - super(...arguments); - this.resolution = 256; - } - fetchTile(zoom, x, y) { - const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); - const context = canvas.getContext('2d'); - const green = new three.Color(0x00ff00); - const red = new three.Color(0xff0000); - const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); - context.fillStyle = color.getStyle(); - context.fillRect(0, 0, this.resolution, this.resolution); - context.fillStyle = '#000000'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; - context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); - context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); - return Promise.resolve(canvas); - } + class DebugProvider extends MapProvider { + constructor() { + super(...arguments); + this.resolution = 256; + } + fetchTile(zoom, x, y) { + const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); + const context = canvas.getContext('2d'); + const green = new three.Color(0x00ff00); + const red = new three.Color(0xff0000); + const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); + context.fillStyle = color.getStyle(); + context.fillRect(0, 0, this.resolution, this.resolution); + context.fillStyle = '#000000'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; + context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); + context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); + return Promise.resolve(canvas); + } } - class HeightDebugProvider extends MapProvider { - constructor(provider) { - super(); - this.fromColor = new three.Color(0xff0000); - this.toColor = new three.Color(0x00ff00); - this.provider = provider; - } - fetchTile(zoom, x, y) { - return __awaiter(this, void 0, void 0, function* () { - const image = yield this.provider.fetchTile(zoom, x, y); - const resolution = 256; - const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); - const imageData = context.getImageData(0, 0, resolution, resolution); - const data = imageData.data; - for (let i = 0; i < data.length; i += 4) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - const max = 1667721.6; - const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); - data[i] = color.r * 255; - data[i + 1] = color.g * 255; - data[i + 2] = color.b * 255; - } - context.putImageData(imageData, 0, 0); - return canvas; - }); - } + class HeightDebugProvider extends MapProvider { + constructor(provider) { + super(); + this.fromColor = new three.Color(0xff0000); + this.toColor = new three.Color(0x00ff00); + this.provider = provider; + } + fetchTile(zoom, x, y) { + return __awaiter(this, void 0, void 0, function* () { + const image = yield this.provider.fetchTile(zoom, x, y); + const resolution = 256; + const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); + const imageData = context.getImageData(0, 0, resolution, resolution); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + const max = 1667721.6; + const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); + data[i] = color.r * 255; + data[i + 1] = color.g * 255; + data[i + 2] = color.b * 255; + } + context.putImageData(imageData, 0, 0); + return canvas; + }); + } } - class GeolocationUtils { - static get() { - return new Promise(function (resolve, reject) { - navigator.geolocation.getCurrentPosition(function (result) { - resolve(result); - }, reject); - }); - } + class GeolocationUtils { + static get() { + return new Promise(function (resolve, reject) { + navigator.geolocation.getCurrentPosition(function (result) { + resolve(result); + }, reject); + }); + } } - class CancelablePromise { - constructor(executor) { - this.fulfilled = false; - this.rejected = false; - this.called = false; - const resolve = (v) => { - this.fulfilled = true; - this.value = v; - if (typeof this.onResolve === 'function') { - this.onResolve(this.value); - this.called = true; - } - }; - const reject = (reason) => { - this.rejected = true; - this.value = reason; - if (typeof this.onReject === 'function') { - this.onReject(this.value); - this.called = true; - } - }; - try { - executor(resolve, reject); - } - catch (error) { - reject(error); - } - } - cancel() { - return false; - } - then(callback) { - this.onResolve = callback; - if (this.fulfilled && !this.called) { - this.called = true; - this.onResolve(this.value); - } - return this; - } - catch(callback) { - this.onReject = callback; - if (this.rejected && !this.called) { - this.called = true; - this.onReject(this.value); - } - return this; - } - finally(callback) { - return this; - } - static resolve(val) { - return new CancelablePromise(function executor(resolve, _reject) { - resolve(val); - }); - } - static reject(reason) { - return new CancelablePromise(function executor(resolve, reject) { - reject(reason); - }); - } - static all(promises) { - const fulfilledPromises = []; - const result = []; - function executor(resolve, reject) { - promises.forEach((promise, index) => { - return promise - .then((val) => { - fulfilledPromises.push(true); - result[index] = val; - if (fulfilledPromises.length === promises.length) { - return resolve(result); - } - }) - .catch((error) => { return reject(error); }); - }); - } - return new CancelablePromise(executor); - } + class CancelablePromise { + constructor(executor) { + this.fulfilled = false; + this.rejected = false; + this.called = false; + const resolve = (v) => { + this.fulfilled = true; + this.value = v; + if (typeof this.onResolve === 'function') { + this.onResolve(this.value); + this.called = true; + } + }; + const reject = (reason) => { + this.rejected = true; + this.value = reason; + if (typeof this.onReject === 'function') { + this.onReject(this.value); + this.called = true; + } + }; + try { + executor(resolve, reject); + } + catch (error) { + reject(error); + } + } + cancel() { + return false; + } + then(callback) { + this.onResolve = callback; + if (this.fulfilled && !this.called) { + this.called = true; + this.onResolve(this.value); + } + return this; + } + catch(callback) { + this.onReject = callback; + if (this.rejected && !this.called) { + this.called = true; + this.onReject(this.value); + } + return this; + } + finally(callback) { + return this; + } + static resolve(val) { + return new CancelablePromise(function executor(resolve, _reject) { + resolve(val); + }); + } + static reject(reason) { + return new CancelablePromise(function executor(resolve, reject) { + reject(reason); + }); + } + static all(promises) { + const fulfilledPromises = []; + const result = []; + function executor(resolve, reject) { + promises.forEach((promise, index) => { + return promise + .then((val) => { + fulfilledPromises.push(true); + result[index] = val; + if (fulfilledPromises.length === promises.length) { + return resolve(result); + } + }) + .catch((error) => { return reject(error); }); + }); + } + return new CancelablePromise(executor); + } } exports.BingMapsProvider = BingMapsProvider; diff --git a/build/geo-three.module.js b/build/geo-three.module.js index 6e9b5f2..68cbb61 100644 --- a/build/geo-three.module.js +++ b/build/geo-three.module.js @@ -1,4 +1,4 @@ -import { Texture, RGBAFormat, LinearFilter, Mesh, REVISION, BufferGeometry, Float32BufferAttribute, Vector2, Vector3, MeshBasicMaterial, MeshPhongMaterial, Vector4, ShaderMaterial, Matrix4, Quaternion, TextureLoader, NearestFilter, Raycaster, DoubleSide, Uint32BufferAttribute, Frustum, Color } from 'three'; +import { Texture, RGBAFormat, LinearFilter, Mesh, BufferGeometry, Float32BufferAttribute, Vector2, Vector3, MeshBasicMaterial, MeshPhongMaterial, Matrix4, Quaternion, NearestFilter, Raycaster, DoubleSide, Uint32BufferAttribute, Frustum, Color } from 'three'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. @@ -25,825 +25,740 @@ function __awaiter(thisArg, _arguments, P, generator) { }); } -class MapProvider { - constructor() { - this.name = ''; - this.minZoom = 0; - this.maxZoom = 20; - this.bounds = []; - this.center = []; - } - fetchTile(zoom, x, y) { - return null; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } +class MapProvider { + constructor() { + this.name = ''; + this.minZoom = 0; + this.maxZoom = 20; + this.bounds = []; + this.center = []; + } + fetchTile(zoom, x, y) { + return null; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } } -class OpenStreetMapsProvider extends MapProvider { - constructor(address = 'https://a.tile.openstreetmap.org/') { - super(); - this.address = address; - this.format = 'png'; - this.maxZoom = 19; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } +class OpenStreetMapsProvider extends MapProvider { + constructor(address = 'https://a.tile.openstreetmap.org/') { + super(); + this.address = address; + this.format = 'png'; + this.maxZoom = 19; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } -class CanvasUtils { - static createOffscreenCanvas(width, height) { - if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(width, height); - } - else { - let canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - } +class CanvasUtils { + static createOffscreenCanvas(width, height) { + if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(width, height); + } + else { + let canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + } + } } -class TextureUtils { - static createFillTexture(color = '#000000', width = 1, height = 1) { - const canvas = CanvasUtils.createOffscreenCanvas(width, height); - const context = canvas.getContext('2d'); - context.fillStyle = color; - context.fillRect(0, 0, width, height); - const texture = new Texture(canvas); - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; - texture.needsUpdate = true; - return texture; - } +class TextureUtils { + static createFillTexture(color = '#000000', width = 1, height = 1) { + const canvas = CanvasUtils.createOffscreenCanvas(width, height); + const context = canvas.getContext('2d'); + context.fillStyle = color; + context.fillRect(0, 0, width, height); + const texture = new Texture(canvas); + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + return texture; + } } -class QuadTreePosition { -} -QuadTreePosition.root = -1; -QuadTreePosition.topLeft = 0; -QuadTreePosition.topRight = 1; -QuadTreePosition.bottomLeft = 2; -QuadTreePosition.bottomRight = 3; -class MapNode extends Mesh { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { - super(geometry, material); - this.mapView = null; - this.parentNode = null; - this.subdivided = false; - this.disposed = false; - this.nodesLoaded = 0; - this.childrenCache = null; - this.isMesh = true; - this.mapView = mapView; - this.parentNode = parentNode; - this.disposed = false; - this.location = location; - this.level = level; - this.x = x; - this.y = y; - this.initialize(); - } - initialize() { - return __awaiter(this, void 0, void 0, function* () { }); - } - createChildNodes() { } - subdivide() { - const maxZoom = this.mapView.maxZoom(); - if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { - return; - } - if (this.mapView.cacheTiles && this.childrenCache !== null) { - this.isMesh = false; - this.children = this.childrenCache; - this.nodesLoaded = this.childrenCache.length; - } - else { - this.createChildNodes(); - } - this.subdivided = true; - } - simplify() { - const minZoom = this.mapView.minZoom(); - if (this.level - 1 < minZoom) { - return; - } - if (this.mapView.cacheTiles) { - this.childrenCache = this.children; - } - else { - for (let i = 0; i < this.children.length; i++) { - this.children[i].dispose(); - } - } - this.subdivided = false; - this.isMesh = true; - this.children = []; - this.nodesLoaded = 0; - } - loadData() { - return __awaiter(this, void 0, void 0, function* () { - if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapNode.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); - } - catch (e) { - if (this.disposed) { - return; - } - console.warn('Geo-Three: Failed to load node tile data.', this); - this.material.map = MapNode.defaultTexture; - } - this.material.needsUpdate = true; - }); - } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new Texture(image); - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } - nodeReady() { - if (this.disposed) { - console.warn('Geo-Three: nodeReady() called for disposed node.', this); - this.dispose(); - return; - } - if (this.parentNode !== null) { - this.parentNode.nodesLoaded++; - if (this.parentNode.nodesLoaded === MapNode.childrens) { - if (this.parentNode.subdivided === true) { - this.parentNode.isMesh = false; - } - for (let i = 0; i < this.parentNode.children.length; i++) { - this.parentNode.children[i].visible = true; - } - } - if (this.parentNode.nodesLoaded > MapNode.childrens) { - console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); - } - } - else { - this.visible = true; - } - } - dispose() { - this.disposed = true; - const self = this; - try { - const material = self.material; - material.dispose(); - if (material.map && material.map !== MapNode.defaultTexture) { - material.map.dispose(); - } - } - catch (e) { } - try { - self.geometry.dispose(); - } - catch (e) { } - } -} -MapNode.defaultTexture = TextureUtils.createFillTexture(); -MapNode.baseGeometry = null; -MapNode.baseScale = null; +class QuadTreePosition { +} +QuadTreePosition.root = -1; +QuadTreePosition.topLeft = 0; +QuadTreePosition.topRight = 1; +QuadTreePosition.bottomLeft = 2; +QuadTreePosition.bottomRight = 3; +class MapNode extends Mesh { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = null, material = null) { + super(geometry, material); + this.mapView = null; + this.parentNode = null; + this.subdivided = false; + this.disposed = false; + this.nodesLoaded = 0; + this.childrenCache = null; + this.isMesh = true; + this.mapView = mapView; + this.parentNode = parentNode; + this.disposed = false; + this.location = location; + this.level = level; + this.x = x; + this.y = y; + this.initialize(); + } + initialize() { + return __awaiter(this, void 0, void 0, function* () { }); + } + createChildNodes() { } + subdivide() { + const maxZoom = this.mapView.maxZoom(); + if (this.children.length > 0 || this.level + 1 > maxZoom || this.parentNode !== null && this.parentNode.nodesLoaded < MapNode.childrens) { + return; + } + if (this.mapView.cacheTiles && this.childrenCache !== null) { + this.isMesh = false; + this.children = this.childrenCache; + this.nodesLoaded = this.childrenCache.length; + } + else { + this.createChildNodes(); + } + this.subdivided = true; + } + simplify() { + const minZoom = this.mapView.minZoom(); + if (this.level - 1 < minZoom) { + return; + } + if (this.mapView.cacheTiles) { + this.childrenCache = this.children; + } + else { + for (let i = 0; i < this.children.length; i++) { + this.children[i].dispose(); + } + } + this.subdivided = false; + this.isMesh = true; + this.children = []; + this.nodesLoaded = 0; + } + loadData() { + return __awaiter(this, void 0, void 0, function* () { + if (this.level < this.mapView.provider.minZoom || this.level > this.mapView.provider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapNode.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.warn('Geo-Three: Failed to load node tile data.', this); + this.material.map = MapNode.defaultTexture; + } + this.material.needsUpdate = true; + }); + } + nodeReady() { + if (this.disposed) { + console.warn('Geo-Three: nodeReady() called for disposed node.', this); + this.dispose(); + return; + } + if (this.parentNode !== null) { + this.parentNode.nodesLoaded++; + if (this.parentNode.nodesLoaded === MapNode.childrens) { + if (this.parentNode.subdivided === true) { + this.parentNode.isMesh = false; + } + for (let i = 0; i < this.parentNode.children.length; i++) { + this.parentNode.children[i].visible = true; + } + } + if (this.parentNode.nodesLoaded > MapNode.childrens) { + console.error('Geo-Three: Loaded more children objects than expected.', this.parentNode.nodesLoaded, this); + } + } + else { + this.visible = true; + } + } + dispose() { + this.disposed = true; + const self = this; + try { + const material = self.material; + material.dispose(); + if (material.map && material.map !== MapNode.defaultTexture) { + material.map.dispose(); + } + } + catch (e) { } + try { + self.geometry.dispose(); + } + catch (e) { } + } +} +MapNode.defaultTexture = TextureUtils.createFillTexture(); +MapNode.baseGeometry = null; +MapNode.baseScale = null; MapNode.childrens = 4; -class MapNodeGeometry extends BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - } - static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - vertices.push(x, 0, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1 - iz / heightSegments); - } - } - for (let iz = 0; iz < heightSegments; iz++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix + gridX * iz; - const b = ix + gridX * (iz + 1); - const c = ix + 1 + gridX * (iz + 1); - const d = ix + 1 + gridX * iz; - indices.push(a, b, d, b, c, d); - } - } - } - static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { - const widthHalf = width / 2; - const heightHalf = height / 2; - const gridX = widthSegments + 1; - const gridZ = heightSegments + 1; - const segmentWidth = width / widthSegments; - const segmentHeight = height / heightSegments; - let start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = -heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 1); - } - for (let ix = 0; ix < widthSegments; ix++) { - const a = ix; - const d = ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(d, b, a, d, c, b); - } - start = vertices.length / 3; - for (let ix = 0; ix < gridX; ix++) { - const x = ix * segmentWidth - widthHalf; - const z = heightSegments * segmentHeight - heightHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(ix / widthSegments, 0); - } - let offset = gridX * gridZ - widthSegments - 1; - for (let ix = 0; ix < widthSegments; ix++) { - const a = offset + ix; - const d = offset + ix + 1; - const b = ix + start; - const c = ix + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = -widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ; - const d = (iz + 1) * gridZ; - const b = iz + start; - const c = iz + start + 1; - indices.push(a, b, d, b, c, d); - } - start = vertices.length / 3; - for (let iz = 0; iz < gridZ; iz++) { - const z = iz * segmentHeight - heightHalf; - const x = widthSegments * segmentWidth - widthHalf; - vertices.push(x, -skirtDepth, z); - normals.push(0, 1, 0); - uvs.push(1.0, 1 - iz / heightSegments); - } - for (let iz = 0; iz < heightSegments; iz++) { - const a = iz * gridZ + heightSegments; - const d = (iz + 1) * gridZ + heightSegments; - const b = iz + start; - const c = iz + start + 1; - indices.push(d, b, a, d, c, b); - } - } +class MapNodeGeometry extends BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + } + static buildPlane(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + vertices.push(x, 0, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1 - iz / heightSegments); + } + } + for (let iz = 0; iz < heightSegments; iz++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix + gridX * iz; + const b = ix + gridX * (iz + 1); + const c = ix + 1 + gridX * (iz + 1); + const d = ix + 1 + gridX * iz; + indices.push(a, b, d, b, c, d); + } + } + } + static buildSkirt(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirtDepth, indices, vertices, normals, uvs) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const gridX = widthSegments + 1; + const gridZ = heightSegments + 1; + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + let start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = -heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 1); + } + for (let ix = 0; ix < widthSegments; ix++) { + const a = ix; + const d = ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(d, b, a, d, c, b); + } + start = vertices.length / 3; + for (let ix = 0; ix < gridX; ix++) { + const x = ix * segmentWidth - widthHalf; + const z = heightSegments * segmentHeight - heightHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(ix / widthSegments, 0); + } + let offset = gridX * gridZ - widthSegments - 1; + for (let ix = 0; ix < widthSegments; ix++) { + const a = offset + ix; + const d = offset + ix + 1; + const b = ix + start; + const c = ix + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = -widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ; + const d = (iz + 1) * gridZ; + const b = iz + start; + const c = iz + start + 1; + indices.push(a, b, d, b, c, d); + } + start = vertices.length / 3; + for (let iz = 0; iz < gridZ; iz++) { + const z = iz * segmentHeight - heightHalf; + const x = widthSegments * segmentWidth - widthHalf; + vertices.push(x, -skirtDepth, z); + normals.push(0, 1, 0); + uvs.push(1.0, 1 - iz / heightSegments); + } + for (let iz = 0; iz < heightSegments; iz++) { + const a = iz * gridZ + heightSegments; + const d = (iz + 1) * gridZ + heightSegments; + const b = iz + start; + const c = iz + start + 1; + indices.push(d, b, a, d, c, b); + } + } } -class Geolocation { - constructor(latitude, longitude) { - this.latitude = latitude; - this.longitude = longitude; - } +class Geolocation { + constructor(latitude, longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } -class UnitsUtils { - static datumsToSpherical(latitude, longitude) { - const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; - let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); - y = y * UnitsUtils.EARTH_ORIGIN / 180.0; - return new Vector2(x, y); - } - static sphericalToDatums(x, y) { - const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; - let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; - latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); - return new Geolocation(latitude, longitude); - } - static quadtreeToDatums(zoom, x, y) { - const n = Math.pow(2.0, zoom); - const longitude = x / n * 360.0 - 180.0; - const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); - const latitude = 180.0 * (latitudeRad / Math.PI); - return new Geolocation(latitude, longitude); - } - static vectorToDatums(dir) { - const radToDeg = 180 / Math.PI; - const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; - const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; - return new Geolocation(latitude, longitude); - } - static datumsToVector(latitude, longitude) { - const degToRad = Math.PI / 180; - const rotX = longitude * degToRad; - const rotY = latitude * degToRad; - var cos = Math.cos(rotY); - return new Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); - } - static mapboxAltitude(color) { - return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; - } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } -} -UnitsUtils.EARTH_RADIUS = 6371008; -UnitsUtils.EARTH_RADIUS_A = 6378137.0; -UnitsUtils.EARTH_RADIUS_B = 6356752.314245; -UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; +class UnitsUtils { + static datumsToSpherical(latitude, longitude) { + const x = longitude * UnitsUtils.EARTH_ORIGIN / 180.0; + let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); + y = y * UnitsUtils.EARTH_ORIGIN / 180.0; + return new Vector2(x, y); + } + static sphericalToDatums(x, y) { + const longitude = x / UnitsUtils.EARTH_ORIGIN * 180.0; + let latitude = y / UnitsUtils.EARTH_ORIGIN * 180.0; + latitude = 180.0 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180.0)) - Math.PI / 2.0); + return new Geolocation(latitude, longitude); + } + static quadtreeToDatums(zoom, x, y) { + const n = Math.pow(2.0, zoom); + const longitude = x / n * 360.0 - 180.0; + const latitudeRad = Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * y / n))); + const latitude = 180.0 * (latitudeRad / Math.PI); + return new Geolocation(latitude, longitude); + } + static vectorToDatums(dir) { + const radToDeg = 180 / Math.PI; + const latitude = Math.atan2(dir.y, Math.sqrt(Math.pow(dir.x, 2) + Math.pow(-dir.z, 2))) * radToDeg; + const longitude = Math.atan2(-dir.z, dir.x) * radToDeg; + return new Geolocation(latitude, longitude); + } + static datumsToVector(latitude, longitude) { + const degToRad = Math.PI / 180; + const rotX = longitude * degToRad; + const rotY = latitude * degToRad; + var cos = Math.cos(rotY); + return new Vector3(-Math.cos(rotX + Math.PI) * cos, Math.sin(rotY), Math.sin(rotX + Math.PI) * cos); + } + static mapboxAltitude(color) { + return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; + } +} +UnitsUtils.EARTH_RADIUS = 6371008; +UnitsUtils.EARTH_RADIUS_A = 6378137.0; +UnitsUtils.EARTH_RADIUS_B = 6356752.314245; +UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; -UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; -class MapPlaneNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new MeshBasicMaterial({ wireframe: false })); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); -MapPlaneNode.baseGeometry = MapPlaneNode.geometry; +class MapPlaneNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapPlaneNode.geometry, new MeshBasicMaterial({ wireframe: false })); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapPlaneNode.geometry = new MapNodeGeometry(1, 1, 1, 1, false); +MapPlaneNode.baseGeometry = MapPlaneNode.geometry; MapPlaneNode.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1.0, UnitsUtils.EARTH_PERIMETER); -class MapNodeHeightGeometry extends BufferGeometry { - constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { - super(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); - const data = imageData.data; - for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - vertices[j + 1] = value; - } - if (skirt) { - MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - if (calculateNormals) { - this.computeNormals(widthSegments, heightSegments); - } - } - computeNormals(widthSegments, heightSegments) { - const positionAttribute = this.getAttribute('position'); - if (positionAttribute !== undefined) { - let normalAttribute = this.getAttribute('normal'); - const normalLength = heightSegments * widthSegments; - for (let i = 0; i < normalLength; i++) { - normalAttribute.setXYZ(i, 0, 0, 0); - } - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); - const indexLength = heightSegments * widthSegments * 6; - for (let i = 0; i < indexLength; i += 3) { - const vA = this.index.getX(i + 0); - const vB = this.index.getX(i + 1); - const vC = this.index.getX(i + 2); - pA.fromBufferAttribute(positionAttribute, vA); - pB.fromBufferAttribute(positionAttribute, vB); - pC.fromBufferAttribute(positionAttribute, vC); - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - nA.fromBufferAttribute(normalAttribute, vA); - nB.fromBufferAttribute(normalAttribute, vB); - nC.fromBufferAttribute(normalAttribute, vC); - nA.add(cb); - nB.add(cb); - nC.add(cb); - normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); - normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); - normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); - } - this.normalizeNormals(); - normalAttribute.needsUpdate = true; - } - } +class MapNodeHeightGeometry extends BufferGeometry { + constructor(width = 1.0, height = 1.0, widthSegments = 1.0, heightSegments = 1.0, skirt = false, skirtDepth = 10.0, imageData = null, calculateNormals = true) { + super(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + MapNodeGeometry.buildPlane(width, height, widthSegments, heightSegments, indices, vertices, normals, uvs); + const data = imageData.data; + for (let i = 0, j = 0; i < data.length && j < vertices.length; i += 4, j += 3) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + vertices[j + 1] = value; + } + if (skirt) { + MapNodeGeometry.buildSkirt(width, height, widthSegments, heightSegments, skirtDepth, indices, vertices, normals, uvs); + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + if (calculateNormals) { + this.computeNormals(widthSegments, heightSegments); + } + } + computeNormals(widthSegments, heightSegments) { + const positionAttribute = this.getAttribute('position'); + if (positionAttribute !== undefined) { + let normalAttribute = this.getAttribute('normal'); + const normalLength = heightSegments * widthSegments; + for (let i = 0; i < normalLength; i++) { + normalAttribute.setXYZ(i, 0, 0, 0); + } + const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); + const cb = new Vector3(), ab = new Vector3(); + const indexLength = heightSegments * widthSegments * 6; + for (let i = 0; i < indexLength; i += 3) { + const vA = this.index.getX(i + 0); + const vB = this.index.getX(i + 1); + const vC = this.index.getX(i + 2); + pA.fromBufferAttribute(positionAttribute, vA); + pB.fromBufferAttribute(positionAttribute, vB); + pC.fromBufferAttribute(positionAttribute, vC); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + nA.fromBufferAttribute(normalAttribute, vA); + nB.fromBufferAttribute(normalAttribute, vB); + nC.fromBufferAttribute(normalAttribute, vC); + nA.add(cb); + nB.add(cb); + nC.add(cb); + normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); + normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); + normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); + } + this.normalizeNormals(); + normalAttribute.needsUpdate = true; + } + } } -class MapHeightNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { - super(parentNode, mapView, location, level, x, y, geometry, material); - this.heightLoaded = false; - this.textureLoaded = false; - this.geometrySize = 16; - this.geometryNormals = false; - this.isMesh = true; - this.visible = false; - this.matrixAutoUpdate = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - yield this.loadHeightGeometry(); - this.nodeReady(); - }); - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.geometry = MapPlaneNode.baseGeometry; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); - const context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); - const imageData = context.getImageData(0, 0, canvas.width, canvas.height); - this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); - } - catch (e) { - if (this.disposed) { - return; - } - this.geometry = MapPlaneNode.baseGeometry; - } - this.heightLoaded = true; - }); - } - createChildNodes() { - const level = this.level + 1; - const Constructor = Object.getPrototypeOf(this).constructor; - const x = this.x * 2; - const y = this.y * 2; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, -0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(-0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - node.scale.set(0.5, 1.0, 0.5); - node.position.set(0.25, 0, 0.25); - this.add(node); - node.updateMatrix(); - node.updateMatrixWorld(true); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapHeightNode.tileSize = 256; -MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); -MapHeightNode.baseGeometry = MapPlaneNode.geometry; +class MapHeightNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, geometry = MapHeightNode.geometry, material = new MeshPhongMaterial({ wireframe: false, color: 0xffffff })) { + super(parentNode, mapView, location, level, x, y, geometry, material); + this.heightLoaded = false; + this.textureLoaded = false; + this.geometrySize = 16; + this.geometryNormals = false; + this.isMesh = true; + this.visible = false; + this.matrixAutoUpdate = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + yield this.loadHeightGeometry(); + this.nodeReady(); + }); + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.geometry = MapPlaneNode.baseGeometry; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const canvas = CanvasUtils.createOffscreenCanvas(this.geometrySize + 1, this.geometrySize + 1); + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, MapHeightNode.tileSize, MapHeightNode.tileSize, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + this.geometry = new MapNodeHeightGeometry(1, 1, this.geometrySize, this.geometrySize, true, 10.0, imageData, true); + } + catch (e) { + if (this.disposed) { + return; + } + this.geometry = MapPlaneNode.baseGeometry; + } + this.heightLoaded = true; + }); + } + createChildNodes() { + const level = this.level + 1; + const Constructor = Object.getPrototypeOf(this).constructor; + const x = this.x * 2; + const y = this.y * 2; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, -0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(-0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + node.scale.set(0.5, 1.0, 0.5); + node.position.set(0.25, 0, 0.25); + this.add(node); + node.updateMatrix(); + node.updateMatrixWorld(true); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapHeightNode.tileSize = 256; +MapHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); +MapHeightNode.baseGeometry = MapPlaneNode.geometry; MapHeightNode.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); -class MapSphereNodeGeometry extends BufferGeometry { - constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { - super(); - const thetaEnd = thetaStart + thetaLength; - let index = 0; - const grid = []; - const vertex = new Vector3(); - const normal = new Vector3(); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - for (let iy = 0; iy <= heightSegments; iy++) { - const verticesRow = []; - const v = iy / heightSegments; - for (let ix = 0; ix <= widthSegments; ix++) { - const u = ix / widthSegments; - vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertex.y = radius * Math.cos(thetaStart + v * thetaLength); - vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - vertices.push(vertex.x, vertex.y, vertex.z); - normal.set(vertex.x, vertex.y, vertex.z).normalize(); - normals.push(normal.x, normal.y, normal.z); - uvs.push(u, 1 - v); - verticesRow.push(index++); - } - grid.push(verticesRow); - } - for (let iy = 0; iy < heightSegments; iy++) { - for (let ix = 0; ix < widthSegments; ix++) { - const a = grid[iy][ix + 1]; - const b = grid[iy][ix]; - const c = grid[iy + 1][ix]; - const d = grid[iy + 1][ix + 1]; - if (iy !== 0 || thetaStart > 0) { - indices.push(a, b, d); - } - if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { - indices.push(b, c, d); - } - } - } - this.setIndex(indices); - this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); - this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); - this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); - } +class MapSphereNodeGeometry extends BufferGeometry { + constructor(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { + super(); + const thetaEnd = thetaStart + thetaLength; + let index = 0; + const grid = []; + const vertex = new Vector3(); + const normal = new Vector3(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + for (let iy = 0; iy <= heightSegments; iy++) { + const verticesRow = []; + const v = iy / heightSegments; + for (let ix = 0; ix <= widthSegments; ix++) { + const u = ix / widthSegments; + vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertex.y = radius * Math.cos(thetaStart + v * thetaLength); + vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + vertices.push(vertex.x, vertex.y, vertex.z); + normal.set(vertex.x, vertex.y, vertex.z).normalize(); + normals.push(normal.x, normal.y, normal.z); + uvs.push(u, 1 - v); + verticesRow.push(index++); + } + grid.push(verticesRow); + } + for (let iy = 0; iy < heightSegments; iy++) { + for (let ix = 0; ix < widthSegments; ix++) { + const a = grid[iy][ix + 1]; + const b = grid[iy][ix]; + const c = grid[iy + 1][ix]; + const d = grid[iy + 1][ix + 1]; + if (iy !== 0 || thetaStart > 0) { + indices.push(a, b, d); + } + if (iy !== heightSegments - 1 || thetaEnd < Math.PI) { + indices.push(b, c, d); + } + } + } + this.setIndex(indices); + this.setAttribute('position', new Float32BufferAttribute(vertices, 3)); + this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); + this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); + } } -class MapSphereNode extends MapNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new Vector4(...bounds); - const material = new ShaderMaterial({ - uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); - this.applyScaleNode(); - this.matrixAutoUpdate = false; - this.isMesh = true; - this.visible = false; - } - initialize() { - const _super = Object.create(null, { - initialize: { get: () => super.initialize } - }); - return __awaiter(this, void 0, void 0, function* () { - _super.initialize.call(this); - yield this.loadData(); - this.nodeReady(); - }); - } - static createGeometry(zoom, x, y) { - const range = Math.pow(2, zoom); - const max = 40; - const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); - return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); - } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } - applyScaleNode() { - this.geometry.computeBoundingBox(); - const box = this.geometry.boundingBox.clone(); - const center = box.getCenter(new Vector3()); - const matrix = new Matrix4(); - matrix.compose(new Vector3(-center.x, -center.y, -center.z), new Quaternion(), new Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); - this.geometry.applyMatrix4(matrix); - this.position.copy(center); - this.updateMatrix(); - this.updateMatrixWorld(); - } - updateMatrix() { - this.matrix.setPosition(this.position); - this.matrixWorldNeedsUpdate = true; - } - updateMatrixWorld(force = false) { - if (this.matrixWorldNeedsUpdate || force) { - this.matrixWorld.copy(this.matrix); - this.matrixWorldNeedsUpdate = false; - } - } - createChildNodes() { - const level = this.level + 1; - const x = this.x * 2; - const y = this.y * 2; - const Constructor = Object.getPrototypeOf(this).constructor; - let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); - this.add(node); - node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); - this.add(node); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - super.raycast(raycaster, intersects); - } - } -} -MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); -MapSphereNode.baseScale = new Vector3(1, 1, 1); +class MapSphereNode extends MapNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); + this.applyScaleNode(); + this.matrixAutoUpdate = false; + this.isMesh = true; + this.visible = false; + } + initialize() { + const _super = Object.create(null, { + initialize: { get: () => super.initialize } + }); + return __awaiter(this, void 0, void 0, function* () { + _super.initialize.call(this); + yield this.loadData(); + this.nodeReady(); + }); + } + static createGeometry(zoom, x, y) { + const range = Math.pow(2, zoom); + const max = 40; + const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; + return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); + } + applyScaleNode() { + this.geometry.computeBoundingBox(); + const box = this.geometry.boundingBox.clone(); + const center = box.getCenter(new Vector3()); + const matrix = new Matrix4(); + matrix.compose(new Vector3(-center.x, -center.y, -center.z), new Quaternion(), new Vector3(UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS, UnitsUtils.EARTH_RADIUS)); + this.geometry.applyMatrix4(matrix); + this.position.copy(center); + this.updateMatrix(); + this.updateMatrixWorld(); + } + updateMatrix() { + this.matrix.setPosition(this.position); + this.matrixWorldNeedsUpdate = true; + } + updateMatrixWorld(force = false) { + if (this.matrixWorldNeedsUpdate || force) { + this.matrixWorld.copy(this.matrix); + this.matrixWorldNeedsUpdate = false; + } + } + createChildNodes() { + const level = this.level + 1; + const x = this.x * 2; + const y = this.y * 2; + const Constructor = Object.getPrototypeOf(this).constructor; + let node = new Constructor(this, this.mapView, QuadTreePosition.topLeft, level, x, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.topRight, level, x + 1, y); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomLeft, level, x, y + 1); + this.add(node); + node = new Constructor(this, this.mapView, QuadTreePosition.bottomRight, level, x + 1, y + 1); + this.add(node); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + super.raycast(raycaster, intersects); + } + } +} +MapSphereNode.baseGeometry = new MapSphereNodeGeometry(UnitsUtils.EARTH_RADIUS, 64, 64, 0, 2 * Math.PI, 0, Math.PI); +MapSphereNode.baseScale = new Vector3(1, 1, 1); MapSphereNode.segments = 80; -class MapHeightNodeShader extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - const material = MapHeightNodeShader.prepareMaterial(new MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); - super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); - this.frustumCulled = false; - } - static prepareMaterial(material) { - material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; - material.onBeforeCompile = (shader) => { - for (const i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = +class MapHeightNodeShader extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { + const material = MapHeightNodeShader.prepareMaterial(new MeshPhongMaterial({ map: MapNode.defaultTexture, color: 0xFFFFFF })); + super(parentNode, mapView, location, level, x, y, MapHeightNodeShader.geometry, material); + this.frustumCulled = false; + } + static prepareMaterial(material) { + material.userData = { heightMap: { value: MapHeightNodeShader.defaultHeightTexture } }; + material.onBeforeCompile = (shader) => { + for (const i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform sampler2D heightMap; - ` + shader.vertexShader; + ` + shader.vertexShader; shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -854,393 +769,389 @@ class MapHeightNodeShader extends MapHeightNode { // Vertex position based on height gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0); - `); - }; - return material; - } - loadData() { - const _super = Object.create(null, { - loadData: { get: () => super.loadData } - }); - return __awaiter(this, void 0, void 0, function* () { - yield _super.loadData.call(this); - this.textureLoaded = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { - console.warn('Geo-Three: Loading tile outside of provider range.', this); - this.material.map = MapHeightNodeShader.defaultTexture; - this.material.needsUpdate = true; - return; - } - try { - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - const texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = NearestFilter; - texture.minFilter = NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - } - catch (e) { - if (this.disposed) { - return; - } - console.error('Geo-Three: Failed to load node tile height data.', this); - this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; - } - this.material.needsUpdate = true; - this.heightLoaded = true; - }); - } - raycast(raycaster, intersects) { - if (this.isMesh === true) { - this.geometry = MapPlaneNode.geometry; - super.raycast(raycaster, intersects); - this.geometry = MapHeightNodeShader.geometry; - } - } - dispose() { - super.dispose(); - if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { - this.material.userData.heightMap.value.dispose(); - } - } -} -MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); -MapHeightNodeShader.geometrySize = 256; -MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); -MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; + `); + }; + return material; + } + loadData() { + const _super = Object.create(null, { + loadData: { get: () => super.loadData } + }); + return __awaiter(this, void 0, void 0, function* () { + yield _super.loadData.call(this); + this.textureLoaded = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + if (this.level < this.mapView.heightProvider.minZoom || this.level > this.mapView.heightProvider.maxZoom) { + console.warn('Geo-Three: Loading tile outside of provider range.', this); + this.material.map = MapHeightNodeShader.defaultTexture; + this.material.needsUpdate = true; + return; + } + try { + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + } + catch (e) { + if (this.disposed) { + return; + } + console.error('Geo-Three: Failed to load node tile height data.', this); + this.material.userData.heightMap.value = MapHeightNodeShader.defaultHeightTexture; + } + this.material.needsUpdate = true; + this.heightLoaded = true; + }); + } + raycast(raycaster, intersects) { + if (this.isMesh === true) { + this.geometry = MapPlaneNode.geometry; + super.raycast(raycaster, intersects); + this.geometry = MapHeightNodeShader.geometry; + } + } + dispose() { + super.dispose(); + if (this.material.userData.heightMap.value && this.material.userData.heightMap.value !== MapHeightNodeShader.defaultHeightTexture) { + this.material.userData.heightMap.value.dispose(); + } + } +} +MapHeightNodeShader.defaultHeightTexture = TextureUtils.createFillTexture('#0186C0'); +MapHeightNodeShader.geometrySize = 256; +MapHeightNodeShader.geometry = new MapNodeGeometry(1.0, 1.0, MapHeightNodeShader.geometrySize, MapHeightNodeShader.geometrySize, true); +MapHeightNodeShader.baseGeometry = MapPlaneNode.geometry; MapHeightNodeShader.baseScale = new Vector3(UnitsUtils.EARTH_PERIMETER, 1, UnitsUtils.EARTH_PERIMETER); -class LODRaycast { - constructor() { - this.subdivisionRays = 1; - this.thresholdUp = 0.6; - this.thresholdDown = 0.15; - this.raycaster = new Raycaster(); - this.mouse = new Vector2(); - this.powerDistance = false; - this.scaleDistance = true; - } - updateLOD(view, camera, renderer, scene) { - const intersects = []; - for (let t = 0; t < this.subdivisionRays; t++) { - this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); - this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } - } - for (let i = 0; i < intersects.length; i++) { - const node = intersects[i].object; - let distance = intersects[i].distance; - if (this.powerDistance) { - distance = Math.pow(distance * 2, node.level); - } - if (this.scaleDistance) { - const matrix = node.matrixWorld.elements; - const vector = new Vector3(matrix[0], matrix[1], matrix[2]); - distance = vector.length() / distance; - } - if (distance > this.thresholdUp) { - node.subdivide(); - } - else if (distance < this.thresholdDown && node.parentNode) { - node.parentNode.simplify(); - } - } - } +class LODRaycast { + constructor() { + this.subdivisionRays = 1; + this.thresholdUp = 0.6; + this.thresholdDown = 0.15; + this.raycaster = new Raycaster(); + this.mouse = new Vector2(); + this.powerDistance = false; + this.scaleDistance = true; + } + updateLOD(view, camera, renderer, scene) { + const intersects = []; + for (let t = 0; t < this.subdivisionRays; t++) { + this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); + this.raycaster.setFromCamera(this.mouse, camera); + this.raycaster.intersectObjects(view.children, true, intersects); + } + for (let i = 0; i < intersects.length; i++) { + const node = intersects[i].object; + let distance = intersects[i].distance; + if (this.powerDistance) { + distance = Math.pow(distance * 2, node.level); + } + if (this.scaleDistance) { + const matrix = node.matrixWorld.elements; + const vector = new Vector3(matrix[0], matrix[1], matrix[2]); + distance = vector.length() / distance; + } + if (distance > this.thresholdUp) { + node.subdivide(); + } + else if (distance < this.thresholdDown && node.parentNode) { + node.parentNode.simplify(); + } + } + } } -class Martini { - constructor(gridSize = 257) { - this.gridSize = gridSize; - const tileSize = gridSize - 1; - if (tileSize & tileSize - 1) { - throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); - } - this.numTriangles = tileSize * tileSize * 2 - 2; - this.numParentTriangles = this.numTriangles - tileSize * tileSize; - this.indices = new Uint32Array(this.gridSize * this.gridSize); - this.coords = new Uint16Array(this.numTriangles * 4); - for (let i = 0; i < this.numTriangles; i++) { - let id = i + 2; - let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; - if (id & 1) { - bx = by = cx = tileSize; - } - else { - ax = ay = cy = tileSize; - } - while ((id >>= 1) > 1) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (id & 1) { - bx = ax; - by = ay; - ax = cx; - ay = cy; - } - else { - ax = bx; - ay = by; - bx = cx; - by = cy; - } - cx = mx; - cy = my; - } - const k = i * 4; - this.coords[k + 0] = ax; - this.coords[k + 1] = ay; - this.coords[k + 2] = bx; - this.coords[k + 3] = by; - } - } - createTile(terrain) { - return new Tile(terrain, this); - } -} -class Tile { - constructor(terrain, martini) { - const size = martini.gridSize; - if (terrain.length !== size * size) { - throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); - } - this.terrain = terrain; - this.martini = martini; - this.errors = new Float32Array(terrain.length); - this.update(); - } - update() { - const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; - const { terrain, errors } = this; - for (let i = numTriangles - 1; i >= 0; i--) { - const k = i * 4; - const ax = coords[k + 0]; - const ay = coords[k + 1]; - const bx = coords[k + 2]; - const by = coords[k + 3]; - const mx = ax + bx >> 1; - const my = ay + by >> 1; - const cx = mx + my - ay; - const cy = my + ax - mx; - const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; - const middleIndex = my * size + mx; - const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); - errors[middleIndex] = Math.max(errors[middleIndex], middleError); - if (i < numParentTriangles) { - const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); - const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); - errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); - } - } - } - getMesh(maxError = 0, withSkirts = false) { - const { gridSize: size, indices } = this.martini; - const { errors } = this; - let numVertices = 0; - let numTriangles = 0; - const max = size - 1; - let aIndex, bIndex, cIndex = 0; - const leftSkirtIndices = []; - const rightSkirtIndices = []; - const bottomSkirtIndices = []; - const topSkirtIndices = []; - indices.fill(0); - function countElements(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - countElements(cx, cy, ax, ay, mx, my); - countElements(bx, by, cx, cy, mx, my); - } - else { - aIndex = ay * size + ax; - bIndex = by * size + bx; - cIndex = cy * size + cx; - if (indices[aIndex] === 0) { - if (withSkirts) { - if (ax === 0) { - leftSkirtIndices.push(numVertices); - } - else if (ax === max) { - rightSkirtIndices.push(numVertices); - } - if (ay === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (ay === max) { - topSkirtIndices.push(numVertices); - } - } - indices[aIndex] = ++numVertices; - } - if (indices[bIndex] === 0) { - if (withSkirts) { - if (bx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (bx === max) { - rightSkirtIndices.push(numVertices); - } - if (by === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (by === max) { - topSkirtIndices.push(numVertices); - } - } - indices[bIndex] = ++numVertices; - } - if (indices[cIndex] === 0) { - if (withSkirts) { - if (cx === 0) { - leftSkirtIndices.push(numVertices); - } - else if (cx === max) { - rightSkirtIndices.push(numVertices); - } - if (cy === 0) { - bottomSkirtIndices.push(numVertices); - } - else if (cy === max) { - topSkirtIndices.push(numVertices); - } - } - indices[cIndex] = ++numVertices; - } - numTriangles++; - } - } - countElements(0, 0, max, max, max, 0); - countElements(max, max, 0, 0, 0, max); - let numTotalVertices = numVertices * 2; - let numTotalTriangles = numTriangles * 3; - if (withSkirts) { - numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; - numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; - } - const vertices = new Uint16Array(numTotalVertices); - const triangles = new Uint32Array(numTotalTriangles); - let triIndex = 0; - function processTriangle(ax, ay, bx, by, cx, cy) { - const mx = ax + bx >> 1; - const my = ay + by >> 1; - if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { - processTriangle(cx, cy, ax, ay, mx, my); - processTriangle(bx, by, cx, cy, mx, my); - } - else { - const a = indices[ay * size + ax] - 1; - const b = indices[by * size + bx] - 1; - const c = indices[cy * size + cx] - 1; - vertices[2 * a] = ax; - vertices[2 * a + 1] = ay; - vertices[2 * b] = bx; - vertices[2 * b + 1] = by; - vertices[2 * c] = cx; - vertices[2 * c + 1] = cy; - triangles[triIndex++] = a; - triangles[triIndex++] = b; - triangles[triIndex++] = c; - } - } - processTriangle(0, 0, max, max, max, 0); - processTriangle(max, max, 0, 0, 0, max); - if (withSkirts) { - leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); - rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); - bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); - topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); - let skirtIndex = numVertices * 2; - function constructSkirt(skirt) { - const skirtLength = skirt.length; - for (let i = 0; i < skirtLength - 1; i++) { - const currIndex = skirt[i]; - const nextIndex = skirt[i + 1]; - const currentSkirt = skirtIndex / 2; - const nextSkirt = (skirtIndex + 2) / 2; - vertices[skirtIndex++] = vertices[2 * currIndex]; - vertices[skirtIndex++] = vertices[2 * currIndex + 1]; - triangles[triIndex++] = currIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextIndex; - triangles[triIndex++] = currentSkirt; - triangles[triIndex++] = nextSkirt; - triangles[triIndex++] = nextIndex; - } - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; - vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; - } - constructSkirt(leftSkirtIndices); - constructSkirt(rightSkirtIndices); - constructSkirt(bottomSkirtIndices); - constructSkirt(topSkirtIndices); - } - return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; - } +class Martini { + constructor(gridSize = 257) { + this.gridSize = gridSize; + const tileSize = gridSize - 1; + if (tileSize & tileSize - 1) { + throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`); + } + this.numTriangles = tileSize * tileSize * 2 - 2; + this.numParentTriangles = this.numTriangles - tileSize * tileSize; + this.indices = new Uint32Array(this.gridSize * this.gridSize); + this.coords = new Uint16Array(this.numTriangles * 4); + for (let i = 0; i < this.numTriangles; i++) { + let id = i + 2; + let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; + if (id & 1) { + bx = by = cx = tileSize; + } + else { + ax = ay = cy = tileSize; + } + while ((id >>= 1) > 1) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (id & 1) { + bx = ax; + by = ay; + ax = cx; + ay = cy; + } + else { + ax = bx; + ay = by; + bx = cx; + by = cy; + } + cx = mx; + cy = my; + } + const k = i * 4; + this.coords[k + 0] = ax; + this.coords[k + 1] = ay; + this.coords[k + 2] = bx; + this.coords[k + 3] = by; + } + } + createTile(terrain) { + return new Tile(terrain, this); + } +} +class Tile { + constructor(terrain, martini) { + const size = martini.gridSize; + if (terrain.length !== size * size) { + throw new Error(`Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.`); + } + this.terrain = terrain; + this.martini = martini; + this.errors = new Float32Array(terrain.length); + this.update(); + } + update() { + const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; + const { terrain, errors } = this; + for (let i = numTriangles - 1; i >= 0; i--) { + const k = i * 4; + const ax = coords[k + 0]; + const ay = coords[k + 1]; + const bx = coords[k + 2]; + const by = coords[k + 3]; + const mx = ax + bx >> 1; + const my = ay + by >> 1; + const cx = mx + my - ay; + const cy = my + ax - mx; + const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; + const middleIndex = my * size + mx; + const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); + errors[middleIndex] = Math.max(errors[middleIndex], middleError); + if (i < numParentTriangles) { + const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); + const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); + errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); + } + } + } + getMesh(maxError = 0, withSkirts = false) { + const { gridSize: size, indices } = this.martini; + const { errors } = this; + let numVertices = 0; + let numTriangles = 0; + const max = size - 1; + let aIndex, bIndex, cIndex = 0; + const leftSkirtIndices = []; + const rightSkirtIndices = []; + const bottomSkirtIndices = []; + const topSkirtIndices = []; + indices.fill(0); + function countElements(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + countElements(cx, cy, ax, ay, mx, my); + countElements(bx, by, cx, cy, mx, my); + } + else { + aIndex = ay * size + ax; + bIndex = by * size + bx; + cIndex = cy * size + cx; + if (indices[aIndex] === 0) { + if (withSkirts) { + if (ax === 0) { + leftSkirtIndices.push(numVertices); + } + else if (ax === max) { + rightSkirtIndices.push(numVertices); + } + if (ay === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (ay === max) { + topSkirtIndices.push(numVertices); + } + } + indices[aIndex] = ++numVertices; + } + if (indices[bIndex] === 0) { + if (withSkirts) { + if (bx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (bx === max) { + rightSkirtIndices.push(numVertices); + } + if (by === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (by === max) { + topSkirtIndices.push(numVertices); + } + } + indices[bIndex] = ++numVertices; + } + if (indices[cIndex] === 0) { + if (withSkirts) { + if (cx === 0) { + leftSkirtIndices.push(numVertices); + } + else if (cx === max) { + rightSkirtIndices.push(numVertices); + } + if (cy === 0) { + bottomSkirtIndices.push(numVertices); + } + else if (cy === max) { + topSkirtIndices.push(numVertices); + } + } + indices[cIndex] = ++numVertices; + } + numTriangles++; + } + } + countElements(0, 0, max, max, max, 0); + countElements(max, max, 0, 0, 0, max); + let numTotalVertices = numVertices * 2; + let numTotalTriangles = numTriangles * 3; + if (withSkirts) { + numTotalVertices += (leftSkirtIndices.length + rightSkirtIndices.length + bottomSkirtIndices.length + topSkirtIndices.length) * 2; + numTotalTriangles += ((leftSkirtIndices.length - 1) * 2 + (rightSkirtIndices.length - 1) * 2 + (bottomSkirtIndices.length - 1) * 2 + (topSkirtIndices.length - 1) * 2) * 3; + } + const vertices = new Uint16Array(numTotalVertices); + const triangles = new Uint32Array(numTotalTriangles); + let triIndex = 0; + function processTriangle(ax, ay, bx, by, cx, cy) { + const mx = ax + bx >> 1; + const my = ay + by >> 1; + if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { + processTriangle(cx, cy, ax, ay, mx, my); + processTriangle(bx, by, cx, cy, mx, my); + } + else { + const a = indices[ay * size + ax] - 1; + const b = indices[by * size + bx] - 1; + const c = indices[cy * size + cx] - 1; + vertices[2 * a] = ax; + vertices[2 * a + 1] = ay; + vertices[2 * b] = bx; + vertices[2 * b + 1] = by; + vertices[2 * c] = cx; + vertices[2 * c + 1] = cy; + triangles[triIndex++] = a; + triangles[triIndex++] = b; + triangles[triIndex++] = c; + } + } + processTriangle(0, 0, max, max, max, 0); + processTriangle(max, max, 0, 0, 0, max); + if (withSkirts) { + leftSkirtIndices.sort((a, b) => { return vertices[2 * a + 1] - vertices[2 * b + 1]; }); + rightSkirtIndices.sort((a, b) => { return vertices[2 * b + 1] - vertices[2 * a + 1]; }); + bottomSkirtIndices.sort((a, b) => { return vertices[2 * b] - vertices[2 * a]; }); + topSkirtIndices.sort((a, b) => { return vertices[2 * a] - vertices[2 * b]; }); + let skirtIndex = numVertices * 2; + function constructSkirt(skirt) { + const skirtLength = skirt.length; + for (let i = 0; i < skirtLength - 1; i++) { + const currIndex = skirt[i]; + const nextIndex = skirt[i + 1]; + const currentSkirt = skirtIndex / 2; + const nextSkirt = (skirtIndex + 2) / 2; + vertices[skirtIndex++] = vertices[2 * currIndex]; + vertices[skirtIndex++] = vertices[2 * currIndex + 1]; + triangles[triIndex++] = currIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextIndex; + triangles[triIndex++] = currentSkirt; + triangles[triIndex++] = nextSkirt; + triangles[triIndex++] = nextIndex; + } + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1]]; + vertices[skirtIndex++] = vertices[2 * skirt[skirtLength - 1] + 1]; + } + constructSkirt(leftSkirtIndices); + constructSkirt(rightSkirtIndices); + constructSkirt(bottomSkirtIndices); + constructSkirt(topSkirtIndices); + } + return { vertices: vertices, triangles: triangles, numVerticesWithoutSkirts: numVertices }; + } } -class MapMartiniHeightNode extends MapHeightNode { - constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { - super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new MeshPhongMaterial({ - map: MapMartiniHeightNode.emptyTexture, - color: 0xFFFFFF, - side: DoubleSide - }), level, exageration)); - this.elevationDecoder = { - rScaler: 256, - gScaler: 1, - bScaler: 1 / 256, - offset: -32768 - }; - this.exageration = 1.0; - this.meshMaxError = 10; - if (elevationDecoder) { - this.elevationDecoder = elevationDecoder; - } - this.meshMaxError = meshMaxError; - this.exageration = exageration; - this.frustumCulled = false; - } - static prepareMaterial(material, level, exageration = 1.0) { - material.userData = { - heightMap: { value: MapMartiniHeightNode.emptyTexture }, - drawNormals: { value: 0 }, - drawBlack: { value: 0 }, - zoomlevel: { value: level }, - computeNormals: { value: 1 }, - drawTexture: { value: 1 } - }; - material.onBeforeCompile = (shader) => { - for (let i in material.userData) { - shader.uniforms[i] = material.userData[i]; - } - shader.vertexShader = +class MapMartiniHeightNode extends MapHeightNode { + constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0, { elevationDecoder = null, meshMaxError = 10, exageration = 1 } = {}) { + super(parentNode, mapView, location, level, x, y, MapMartiniHeightNode.geometry, MapMartiniHeightNode.prepareMaterial(new MeshPhongMaterial({ + map: MapMartiniHeightNode.emptyTexture, + color: 0xFFFFFF, + side: DoubleSide + }), level, exageration)); + this.elevationDecoder = { + rScaler: 256, + gScaler: 1, + bScaler: 1 / 256, + offset: -32768 + }; + this.exageration = 1.0; + this.meshMaxError = 10; + if (elevationDecoder) { + this.elevationDecoder = elevationDecoder; + } + this.meshMaxError = meshMaxError; + this.exageration = exageration; + this.frustumCulled = false; + } + static prepareMaterial(material, level, exageration = 1.0) { + material.userData = { + heightMap: { value: MapMartiniHeightNode.emptyTexture }, + drawNormals: { value: 0 }, + drawBlack: { value: 0 }, + zoomlevel: { value: level }, + computeNormals: { value: 1 }, + drawTexture: { value: 1 } + }; + material.onBeforeCompile = (shader) => { + for (let i in material.userData) { + shader.uniforms[i] = material.userData[i]; + } + shader.vertexShader = ` uniform bool computeNormals; uniform float zoomlevel; uniform sampler2D heightMap; - ` + shader.vertexShader; - shader.fragmentShader = + ` + shader.vertexShader; + shader.fragmentShader = ` uniform bool drawNormals; uniform bool drawTexture; uniform bool drawBlack; - ` + shader.fragmentShader; + ` + shader.fragmentShader; shader.fragmentShader = shader.fragmentShader.replace('#include ', ` if(drawBlack) { gl_FragColor = vec4( 0.0,0.0,0.0, 1.0 ); @@ -1248,7 +1159,7 @@ class MapMartiniHeightNode extends MapHeightNode { gl_FragColor = vec4( ( 0.5 * vNormal + 0.5 ), 1.0 ); } else if (!drawTexture) { gl_FragColor = vec4( 0.0,0.0,0.0, 0.0 ); - }`); + }`); shader.vertexShader = shader.vertexShader.replace('#include ', ` #include @@ -1291,710 +1202,710 @@ class MapMartiniHeightNode extends MapHeightNode { v2.z = (e+ h + i + f) / 4.0; vNormal = (normalize(cross(v2 - v0, v1 - v0))).rbg; } - `); - }; - return material; - } - static getTerrain(imageData, tileSize, elevation) { - const { rScaler, bScaler, gScaler, offset } = elevation; - const gridSize = tileSize + 1; - const terrain = new Float32Array(gridSize * gridSize); - for (let i = 0, y = 0; y < tileSize; y++) { - for (let x = 0; x < tileSize; x++, i++) { - const k = i * 4; - const r = imageData[k + 0]; - const g = imageData[k + 1]; - const b = imageData[k + 2]; - terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; - } - } - for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { - terrain[i] = terrain[i - gridSize]; - } - for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { - terrain[i] = terrain[i - 1]; - } - return terrain; - } - static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { - const gridSize = tileSize + 1; - const numOfVerticies = vertices.length / 2; - const positions = new Float32Array(numOfVerticies * 3); - const texCoords = new Float32Array(numOfVerticies * 2); - const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; - const xScale = (maxX - minX) / tileSize; - const yScale = (maxY - minY) / tileSize; - for (let i = 0; i < numOfVerticies; i++) { - const x = vertices[i * 2]; - const y = vertices[i * 2 + 1]; - const pixelIdx = y * gridSize + x; - positions[3 * i + 0] = x * xScale + minX; - positions[3 * i + 1] = -terrain[pixelIdx] * exageration; - positions[3 * i + 2] = -y * yScale + maxY; - texCoords[2 * i + 0] = x / tileSize; - texCoords[2 * i + 1] = y / tileSize; - } - return { - position: { value: positions, size: 3 }, - uv: { value: texCoords, size: 2 } - }; - } - processHeight(image) { - return __awaiter(this, void 0, void 0, function* () { - const tileSize = image.width; - const gridSize = tileSize + 1; - var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); - var context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); - const martini = new Martini(gridSize); - const tile = martini.createTile(terrain); - const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); - const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); - this.geometry = new BufferGeometry(); - this.geometry.setIndex(new Uint32BufferAttribute(triangles, 1)); - this.geometry.setAttribute('position', new Float32BufferAttribute(attributes.position.value, attributes.position.size)); - this.geometry.setAttribute('uv', new Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); - this.geometry.rotateX(Math.PI); - var texture = new Texture(image); - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = NearestFilter; - texture.minFilter = NearestFilter; - texture.needsUpdate = true; - this.material.userData.heightMap.value = texture; - this.material.map = texture; - this.material.needsUpdate = true; - }); - } - loadHeightGeometry() { - return __awaiter(this, void 0, void 0, function* () { - if (this.mapView.heightProvider === null) { - throw new Error('GeoThree: MapView.heightProvider provider is null.'); - } - const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); - if (this.disposed) { - return; - } - this.processHeight(image); - this.heightLoaded = true; - this.nodeReady(); - }); - } -} -MapMartiniHeightNode.geometrySize = 16; -MapMartiniHeightNode.emptyTexture = new Texture(); -MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); + `); + }; + return material; + } + static getTerrain(imageData, tileSize, elevation) { + const { rScaler, bScaler, gScaler, offset } = elevation; + const gridSize = tileSize + 1; + const terrain = new Float32Array(gridSize * gridSize); + for (let i = 0, y = 0; y < tileSize; y++) { + for (let x = 0; x < tileSize; x++, i++) { + const k = i * 4; + const r = imageData[k + 0]; + const g = imageData[k + 1]; + const b = imageData[k + 2]; + terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; + } + } + for (let i = gridSize * (gridSize - 1), x = 0; x < gridSize - 1; x++, i++) { + terrain[i] = terrain[i - gridSize]; + } + for (let i = gridSize - 1, y = 0; y < gridSize; y++, i += gridSize) { + terrain[i] = terrain[i - 1]; + } + return terrain; + } + static getMeshAttributes(vertices, terrain, tileSize, bounds, exageration) { + const gridSize = tileSize + 1; + const numOfVerticies = vertices.length / 2; + const positions = new Float32Array(numOfVerticies * 3); + const texCoords = new Float32Array(numOfVerticies * 2); + const [minX, minY, maxX, maxY] = bounds || [0, 0, tileSize, tileSize]; + const xScale = (maxX - minX) / tileSize; + const yScale = (maxY - minY) / tileSize; + for (let i = 0; i < numOfVerticies; i++) { + const x = vertices[i * 2]; + const y = vertices[i * 2 + 1]; + const pixelIdx = y * gridSize + x; + positions[3 * i + 0] = x * xScale + minX; + positions[3 * i + 1] = -terrain[pixelIdx] * exageration; + positions[3 * i + 2] = -y * yScale + maxY; + texCoords[2 * i + 0] = x / tileSize; + texCoords[2 * i + 1] = y / tileSize; + } + return { + position: { value: positions, size: 3 }, + uv: { value: texCoords, size: 2 } + }; + } + processHeight(image) { + return __awaiter(this, void 0, void 0, function* () { + const tileSize = image.width; + const gridSize = tileSize + 1; + var canvas = CanvasUtils.createOffscreenCanvas(tileSize, tileSize); + var context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, tileSize, tileSize, 0, 0, canvas.width, canvas.height); + var imageData = context.getImageData(0, 0, canvas.width, canvas.height); + var data = imageData.data; + const terrain = MapMartiniHeightNode.getTerrain(data, tileSize, this.elevationDecoder); + const martini = new Martini(gridSize); + const tile = martini.createTile(terrain); + const { vertices, triangles } = tile.getMesh(typeof this.meshMaxError === 'function' ? this.meshMaxError(this.level) : this.meshMaxError); + const attributes = MapMartiniHeightNode.getMeshAttributes(vertices, terrain, tileSize, [-0.5, -0.5, 0.5, 0.5], this.exageration); + this.geometry = new BufferGeometry(); + this.geometry.setIndex(new Uint32BufferAttribute(triangles, 1)); + this.geometry.setAttribute('position', new Float32BufferAttribute(attributes.position.value, attributes.position.size)); + this.geometry.setAttribute('uv', new Float32BufferAttribute(attributes.uv.value, attributes.uv.size)); + this.geometry.rotateX(Math.PI); + var texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + texture.needsUpdate = true; + this.material.userData.heightMap.value = texture; + this.material.map = texture; + this.material.needsUpdate = true; + }); + } + loadHeightGeometry() { + return __awaiter(this, void 0, void 0, function* () { + if (this.mapView.heightProvider === null) { + throw new Error('GeoThree: MapView.heightProvider provider is null.'); + } + const image = yield this.mapView.heightProvider.fetchTile(this.level, this.x, this.y); + if (this.disposed) { + return; + } + this.processHeight(image); + this.heightLoaded = true; + this.nodeReady(); + }); + } +} +MapMartiniHeightNode.geometrySize = 16; +MapMartiniHeightNode.emptyTexture = new Texture(); +MapMartiniHeightNode.geometry = new MapNodeGeometry(1, 1, 1, 1); MapMartiniHeightNode.tileSize = 256; -class MapView extends Mesh { - constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { - super(undefined, new MeshBasicMaterial({ transparent: true, opacity: 0.0, depthWrite: false, colorWrite: false })); - this.lod = null; - this.provider = null; - this.heightProvider = null; - this.root = null; - this.cacheTiles = false; - this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { - this.lod.updateLOD(this, camera, renderer, scene); - }; - this.lod = new LODRaycast(); - this.provider = provider; - this.heightProvider = heightProvider; - this.setRoot(root); - this.preSubdivide(); - } - setRoot(root) { - if (typeof root === 'number') { - if (!MapView.mapModes.has(root)) { - throw new Error('Map mode ' + root + ' does is not registered.'); - } - const rootConstructor = MapView.mapModes.get(root); - root = new rootConstructor(null, this); - } - if (this.root !== null) { - this.remove(this.root); - this.root = null; - } - this.root = root; - if (this.root !== null) { - this.geometry = this.root.constructor.baseGeometry; - this.scale.copy(this.root.constructor.baseScale); - this.root.mapView = this; - this.add(this.root); - this.root.initialize(); - } - } - preSubdivide() { - var _a, _b; - function subdivide(node, depth) { - if (depth <= 0) { - return; - } - node.subdivide(); - for (let i = 0; i < node.children.length; i++) { - if (node.children[i] instanceof MapNode) { - const child = node.children[i]; - subdivide(child, depth - 1); - } - } - } - const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - if (minZoom > 0) { - subdivide(this.root, minZoom); - } - } - setProvider(provider) { - if (provider !== this.provider) { - this.provider = provider; - this.clear(); - } - } - setHeightProvider(heightProvider) { - if (heightProvider !== this.heightProvider) { - this.heightProvider = heightProvider; - this.clear(); - } - } - clear() { - this.traverse(function (children) { - if (children.childrenCache) { - children.childrenCache = null; - } - if (children.initialize) { - children.initialize(); - } - }); - return this; - } - minZoom() { - var _a, _b; - return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); - } - maxZoom() { - var _a, _b; - return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); - } - getMetaData() { - this.provider.getMetaData(); - } - raycast(raycaster, intersects) { - return false; - } -} -MapView.PLANAR = 200; -MapView.SPHERICAL = 201; -MapView.HEIGHT = 202; -MapView.HEIGHT_SHADER = 203; -MapView.MARTINI = 204; -MapView.mapModes = new Map([ - [MapView.PLANAR, MapPlaneNode], - [MapView.SPHERICAL, MapSphereNode], - [MapView.HEIGHT, MapHeightNode], - [MapView.HEIGHT_SHADER, MapHeightNodeShader], - [MapView.MARTINI, MapMartiniHeightNode] +class MapView extends Mesh { + constructor(root = MapView.PLANAR, provider = new OpenStreetMapsProvider(), heightProvider = null) { + super(undefined, new MeshBasicMaterial({ transparent: true, opacity: 0.0 })); + this.lod = null; + this.provider = null; + this.heightProvider = null; + this.root = null; + this.cacheTiles = false; + this.onBeforeRender = (renderer, scene, camera, geometry, material, group) => { + this.lod.updateLOD(this, camera, renderer, scene); + }; + this.lod = new LODRaycast(); + this.provider = provider; + this.heightProvider = heightProvider; + this.setRoot(root); + this.preSubdivide(); + } + setRoot(root) { + if (typeof root === 'number') { + if (!MapView.mapModes.has(root)) { + throw new Error('Map mode ' + root + ' does is not registered.'); + } + const rootConstructor = MapView.mapModes.get(root); + root = new rootConstructor(null, this); + } + if (this.root !== null) { + this.remove(this.root); + this.root = null; + } + this.root = root; + if (this.root !== null) { + this.geometry = this.root.constructor.baseGeometry; + this.scale.copy(this.root.constructor.baseScale); + this.root.mapView = this; + this.add(this.root); + this.root.initialize(); + } + } + preSubdivide() { + var _a, _b; + function subdivide(node, depth) { + if (depth <= 0) { + return; + } + node.subdivide(); + for (let i = 0; i < node.children.length; i++) { + if (node.children[i] instanceof MapNode) { + const child = node.children[i]; + subdivide(child, depth - 1); + } + } + } + const minZoom = Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + if (minZoom > 0) { + subdivide(this.root, minZoom); + } + } + setProvider(provider) { + if (provider !== this.provider) { + this.provider = provider; + this.clear(); + } + } + setHeightProvider(heightProvider) { + if (heightProvider !== this.heightProvider) { + this.heightProvider = heightProvider; + this.clear(); + } + } + clear() { + this.traverse(function (children) { + if (children.childrenCache) { + children.childrenCache = null; + } + if (children.initialize) { + children.initialize(); + } + }); + return this; + } + minZoom() { + var _a, _b; + return Math.max(this.provider.minZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.minZoom) !== null && _b !== void 0 ? _b : -Infinity); + } + maxZoom() { + var _a, _b; + return Math.min(this.provider.maxZoom, (_b = (_a = this.heightProvider) === null || _a === void 0 ? void 0 : _a.maxZoom) !== null && _b !== void 0 ? _b : Infinity); + } + getMetaData() { + this.provider.getMetaData(); + } + raycast(raycaster, intersects) { + return false; + } +} +MapView.PLANAR = 200; +MapView.SPHERICAL = 201; +MapView.HEIGHT = 202; +MapView.HEIGHT_SHADER = 203; +MapView.MARTINI = 204; +MapView.mapModes = new Map([ + [MapView.PLANAR, MapPlaneNode], + [MapView.SPHERICAL, MapSphereNode], + [MapView.HEIGHT, MapHeightNode], + [MapView.HEIGHT_SHADER, MapHeightNodeShader], + [MapView.MARTINI, MapMartiniHeightNode] ]); -const pov$1 = new Vector3(); -const position$1 = new Vector3(); -class LODRadial { - constructor(subdivideDistance = 50, simplifyDistance = 300) { - this.subdivideDistance = subdivideDistance; - this.simplifyDistance = simplifyDistance; - } - updateLOD(view, camera, renderer, scene) { - camera.getWorldPosition(pov$1); - view.children[0].traverse((node) => { - node.getWorldPosition(position$1); - let distance = pov$1.distanceTo(position$1); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - if (distance < this.subdivideDistance) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } +const pov$1 = new Vector3(); +const position$1 = new Vector3(); +class LODRadial { + constructor(subdivideDistance = 50, simplifyDistance = 300) { + this.subdivideDistance = subdivideDistance; + this.simplifyDistance = simplifyDistance; + } + updateLOD(view, camera, renderer, scene) { + camera.getWorldPosition(pov$1); + view.children[0].traverse((node) => { + node.getWorldPosition(position$1); + let distance = pov$1.distanceTo(position$1); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + if (distance < this.subdivideDistance) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } -const projection = new Matrix4(); -const pov = new Vector3(); -const frustum = new Frustum(); -const position = new Vector3(); -class LODFrustum extends LODRadial { - constructor(subdivideDistance = 120, simplifyDistance = 400) { - super(subdivideDistance, simplifyDistance); - this.testCenter = true; - this.pointOnly = false; - } - updateLOD(view, camera, renderer, scene) { - projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromProjectionMatrix(projection); - camera.getWorldPosition(pov); - view.children[0].traverse((node) => { - node.getWorldPosition(position); - let distance = pov.distanceTo(position); - distance /= Math.pow(2, view.provider.maxZoom - node.level); - const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); - if (distance < this.subdivideDistance && inFrustum) { - node.subdivide(); - } - else if (distance > this.simplifyDistance && node.parentNode) { - node.parentNode.simplify(); - } - }); - } +const projection = new Matrix4(); +const pov = new Vector3(); +const frustum = new Frustum(); +const position = new Vector3(); +class LODFrustum extends LODRadial { + constructor(subdivideDistance = 120, simplifyDistance = 400) { + super(subdivideDistance, simplifyDistance); + this.testCenter = true; + this.pointOnly = false; + } + updateLOD(view, camera, renderer, scene) { + projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projection); + camera.getWorldPosition(pov); + view.children[0].traverse((node) => { + node.getWorldPosition(position); + let distance = pov.distanceTo(position); + distance /= Math.pow(2, view.provider.maxZoom - node.level); + const inFrustum = this.pointOnly ? frustum.containsPoint(position) : frustum.intersectsObject(node); + if (distance < this.subdivideDistance && inFrustum) { + node.subdivide(); + } + else if (distance > this.simplifyDistance && node.parentNode) { + node.parentNode.simplify(); + } + }); + } } -class XHRUtils { - static get(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static getRaw(url) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.open('GET', url, true); - xhr.onload = function () { - resolve(xhr.response); - }; - xhr.onerror = reject; - xhr.send(null); - }); - }); - } - static request(url, type, header, body, onLoad, onError, onProgress) { - function parseResponse(response) { - try { - return JSON.parse(response); - } - catch (e) { - return response; - } - } - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('text/plain'); - xhr.open(type, url, true); - if (header !== null && header !== undefined) { - for (const i in header) { - xhr.setRequestHeader(i, header[i]); - } - } - if (onLoad !== undefined) { - xhr.onload = function (event) { - onLoad(parseResponse(xhr.response), xhr); - }; - } - if (onError !== undefined) { - xhr.onerror = onError; - } - if (onProgress !== undefined) { - xhr.onprogress = onProgress; - } - xhr.send(body !== undefined ? body : null); - return xhr; - } +class XHRUtils { + static get(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static getRaw(url) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.open('GET', url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.onerror = reject; + xhr.send(null); + }); + }); + } + static request(url, type, header, body, onLoad, onError, onProgress) { + function parseResponse(response) { + try { + return JSON.parse(response); + } + catch (e) { + return response; + } + } + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('text/plain'); + xhr.open(type, url, true); + if (header !== null && header !== undefined) { + for (const i in header) { + xhr.setRequestHeader(i, header[i]); + } + } + if (onLoad !== undefined) { + xhr.onload = function (event) { + onLoad(parseResponse(xhr.response), xhr); + }; + } + if (onError !== undefined) { + xhr.onerror = onError; + } + if (onProgress !== undefined) { + xhr.onprogress = onProgress; + } + xhr.send(body !== undefined ? body : null); + return xhr; + } } -class BingMapsProvider extends MapProvider { - constructor(apiKey = '', type = BingMapsProvider.AERIAL) { - super(); - this.maxZoom = 19; - this.minZoom = 1; - this.format = 'jpeg'; - this.mapSize = 512; - this.subdomain = 't1'; - this.meta = null; - this.apiKey = apiKey; - this.type = type; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; - const data = yield XHRUtils.get(address); - this.meta = JSON.parse(data); - }); - } - static quadKey(zoom, x, y) { - let quad = ''; - for (let i = zoom; i > 0; i--) { - const mask = 1 << i - 1; - let cell = 0; - if ((x & mask) !== 0) { - cell++; - } - if ((y & mask) !== 0) { - cell += 2; - } - quad += cell; - } - return quad; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; - }); - } -} -BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; -BingMapsProvider.AERIAL = 'a'; -BingMapsProvider.ROAD = 'r'; -BingMapsProvider.AERIAL_LABELS = 'h'; -BingMapsProvider.OBLIQUE = 'o'; +class BingMapsProvider extends MapProvider { + constructor(apiKey = '', type = BingMapsProvider.AERIAL) { + super(); + this.maxZoom = 19; + this.minZoom = 1; + this.format = 'jpeg'; + this.mapSize = 512; + this.subdomain = 't1'; + this.meta = null; + this.apiKey = apiKey; + this.type = type; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = BingMapsProvider.ADDRESS + '/REST/V1/Imagery/Metadata/RoadOnDemand?output=json&include=ImageryProviders&key=' + this.apiKey; + const data = yield XHRUtils.get(address); + this.meta = JSON.parse(data); + }); + } + static quadKey(zoom, x, y) { + let quad = ''; + for (let i = zoom; i > 0; i--) { + const mask = 1 << i - 1; + let cell = 0; + if ((x & mask) !== 0) { + cell++; + } + if ((y & mask) !== 0) { + cell += 2; + } + quad += cell; + } + return quad; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'http://ecn.' + this.subdomain + '.tiles.virtualearth.net/tiles/' + this.type + BingMapsProvider.quadKey(zoom, x, y) + '.jpeg?g=1173'; + }); + } +} +BingMapsProvider.ADDRESS = 'https://dev.virtualearth.net'; +BingMapsProvider.AERIAL = 'a'; +BingMapsProvider.ROAD = 'r'; +BingMapsProvider.AERIAL_LABELS = 'h'; +BingMapsProvider.OBLIQUE = 'o'; BingMapsProvider.OBLIQUE_LABELS = 'b'; -class GoogleMapsProvider extends MapProvider { - constructor(apiToken) { - super(); - this.sessionToken = null; - this.orientation = 0; - this.format = 'png'; - this.mapType = 'roadmap'; - this.overlay = false; - this.apiToken = apiToken !== undefined ? apiToken : ''; - this.createSession(); - } - createSession() { - const address = 'https://tile.googleapis.com/v1/createSession?key=' + this.apiToken; - const data = JSON.stringify({ - mapType: this.mapType, - language: 'en-EN', - region: 'en', - layerTypes: ['layerRoadmap', 'layerStreetview'], - overlay: this.overlay, - scale: 'scaleFactor1x' - }); - XHRUtils.request(address, 'POST', { 'Content-Type': 'text/json' }, data, (response, xhr) => { - this.sessionToken = response.session; - }, function (xhr) { - throw new Error('Unable to create a google maps session.'); - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://tile.googleapis.com/v1/2dtiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; - }); - } +class GoogleMapsProvider extends MapProvider { + constructor(apiToken) { + super(); + this.sessionToken = null; + this.orientation = 0; + this.format = 'png'; + this.mapType = 'roadmap'; + this.overlay = false; + this.apiToken = apiToken !== undefined ? apiToken : ''; + this.createSession(); + } + createSession() { + const address = 'https://www.googleapis.com/tile/v1/createSession?key=' + this.apiToken; + const data = JSON.stringify({ + mapType: this.mapType, + language: 'en-EN', + region: 'en', + layerTypes: ['layerRoadmap', 'layerStreetview'], + overlay: this.overlay, + scale: 'scaleFactor1x' + }); + XHRUtils.request(address, 'GET', { 'Content-Type': 'text/json' }, data, (response, xhr) => { + this.sessionToken = response.session; + }, function (xhr) { + throw new Error('Unable to create a google maps session.'); + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://www.googleapis.com/tile/v1/tiles/' + zoom + '/' + x + '/' + y + '?session=' + this.sessionToken + '&orientation=' + this.orientation + '&key=' + this.apiToken; + }); + } } -class HereMapsProvider extends MapProvider { - constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { - super(); - this.appId = appId; - this.appCode = appCode; - this.style = style; - this.scheme = scheme; - this.format = format; - this.size = size; - this.version = 'newest'; - this.server = 1; - } - nextServer() { - this.server = this.server % 4 === 0 ? 1 : this.server + 1; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { }); - } - fetchTile(zoom, x, y) { - this.nextServer(); - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + - this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + - this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; - }); - } -} +class HereMapsProvider extends MapProvider { + constructor(appId = '', appCode = '', style = 'base', scheme = 'normal.day', format = 'png', size = 512) { + super(); + this.appId = appId; + this.appCode = appCode; + this.style = style; + this.scheme = scheme; + this.format = format; + this.size = size; + this.version = 'newest'; + this.server = 1; + } + nextServer() { + this.server = this.server % 4 === 0 ? 1 : this.server + 1; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { }); + } + fetchTile(zoom, x, y) { + this.nextServer(); + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://' + this.server + '.' + this.style + '.maps.api.here.com/maptile/2.1/maptile/' + + this.version + '/' + this.scheme + '/' + zoom + '/' + x + '/' + y + '/' + + this.size + '/' + this.format + '?app_id=' + this.appId + '&app_code=' + this.appCode; + }); + } +} HereMapsProvider.PATH = '/maptile/2.1/'; -class MapBoxProvider extends MapProvider { - constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { - super(); - this.apiToken = apiToken; - this.format = format; - this.useHDPI = useHDPI; - this.mode = mode; - this.mapId = id; - this.style = id; - this.version = version; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - if (this.mode === MapBoxProvider.STYLE) { - image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; - } - else { - image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; - } - }); - } -} -MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; -MapBoxProvider.STYLE = 100; +class MapBoxProvider extends MapProvider { + constructor(apiToken = '', id = '', mode = MapBoxProvider.STYLE, format = 'png', useHDPI = false, version = 'v4') { + super(); + this.apiToken = apiToken; + this.format = format; + this.useHDPI = useHDPI; + this.mode = mode; + this.mapId = id; + this.style = id; + this.version = version; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = MapBoxProvider.ADDRESS + this.version + '/' + this.mapId + '.json?access_token=' + this.apiToken; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + if (this.mode === MapBoxProvider.STYLE) { + image.src = MapBoxProvider.ADDRESS + 'styles/v1/' + this.style + '/tiles/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x?access_token=' : '?access_token=') + this.apiToken; + } + else { + image.src = MapBoxProvider.ADDRESS + 'v4/' + this.mapId + '/' + zoom + '/' + x + '/' + y + (this.useHDPI ? '@2x.' : '.') + this.format + '?access_token=' + this.apiToken; + } + }); + } +} +MapBoxProvider.ADDRESS = 'https://api.mapbox.com/'; +MapBoxProvider.STYLE = 100; MapBoxProvider.MAP_ID = 101; -class MapTilerProvider extends MapProvider { - constructor(apiKey, category, style, format) { - super(); - this.apiKey = apiKey !== undefined ? apiKey : ''; - this.format = format !== undefined ? format : 'png'; - this.category = category !== undefined ? category : 'maps'; - this.style = style !== undefined ? style : 'satellite'; - this.resolution = 512; - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; - }); - } +class MapTilerProvider extends MapProvider { + constructor(apiKey, category, style, format) { + super(); + this.apiKey = apiKey !== undefined ? apiKey : ''; + this.format = format !== undefined ? format : 'png'; + this.category = category !== undefined ? category : 'maps'; + this.style = style !== undefined ? style : 'satellite'; + this.resolution = 512; + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = 'https://api.maptiler.com/' + this.category + '/' + this.style + '/' + zoom + '/' + x + '/' + y + '.' + this.format + '?key=' + this.apiKey; + }); + } } -class OpenMapTilesProvider extends MapProvider { - constructor(address, format = 'png', theme = 'klokantech-basic') { - super(); - this.address = address; - this.format = format; - this.theme = theme; - } - getMetaData() { - return __awaiter(this, void 0, void 0, function* () { - const address = this.address + 'styles/' + this.theme + '.json'; - const data = yield XHRUtils.get(address); - const meta = JSON.parse(data); - this.name = meta.name; - this.format = meta.format; - this.minZoom = meta.minZoom; - this.maxZoom = meta.maxZoom; - this.bounds = meta.bounds; - this.center = meta.center; - }); - } - fetchTile(zoom, x, y) { - return new Promise((resolve, reject) => { - const image = document.createElement('img'); - image.onload = function () { - resolve(image); - }; - image.onerror = function () { - reject(); - }; - image.crossOrigin = 'Anonymous'; - image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; - }); - } +class OpenMapTilesProvider extends MapProvider { + constructor(address, format = 'png', theme = 'klokantech-basic') { + super(); + this.address = address; + this.format = format; + this.theme = theme; + } + getMetaData() { + return __awaiter(this, void 0, void 0, function* () { + const address = this.address + 'styles/' + this.theme + '.json'; + const data = yield XHRUtils.get(address); + const meta = JSON.parse(data); + this.name = meta.name; + this.format = meta.format; + this.minZoom = meta.minZoom; + this.maxZoom = meta.maxZoom; + this.bounds = meta.bounds; + this.center = meta.center; + }); + } + fetchTile(zoom, x, y) { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + image.onload = function () { + resolve(image); + }; + image.onerror = function () { + reject(); + }; + image.crossOrigin = 'Anonymous'; + image.src = this.address + 'styles/' + this.theme + '/' + zoom + '/' + x + '/' + y + '.' + this.format; + }); + } } -class DebugProvider extends MapProvider { - constructor() { - super(...arguments); - this.resolution = 256; - } - fetchTile(zoom, x, y) { - const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); - const context = canvas.getContext('2d'); - const green = new Color(0x00ff00); - const red = new Color(0xff0000); - const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); - context.fillStyle = color.getStyle(); - context.fillRect(0, 0, this.resolution, this.resolution); - context.fillStyle = '#000000'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; - context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); - context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); - return Promise.resolve(canvas); - } +class DebugProvider extends MapProvider { + constructor() { + super(...arguments); + this.resolution = 256; + } + fetchTile(zoom, x, y) { + const canvas = CanvasUtils.createOffscreenCanvas(this.resolution, this.resolution); + const context = canvas.getContext('2d'); + const green = new Color(0x00ff00); + const red = new Color(0xff0000); + const color = green.lerpHSL(red, (zoom - this.minZoom) / (this.maxZoom - this.minZoom)); + context.fillStyle = color.getStyle(); + context.fillRect(0, 0, this.resolution, this.resolution); + context.fillStyle = '#000000'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = 'bold ' + this.resolution * 0.1 + 'px arial'; + context.fillText('(' + zoom + ')', this.resolution / 2, this.resolution * 0.4); + context.fillText('(' + x + ', ' + y + ')', this.resolution / 2, this.resolution * 0.6); + return Promise.resolve(canvas); + } } -class HeightDebugProvider extends MapProvider { - constructor(provider) { - super(); - this.fromColor = new Color(0xff0000); - this.toColor = new Color(0x00ff00); - this.provider = provider; - } - fetchTile(zoom, x, y) { - return __awaiter(this, void 0, void 0, function* () { - const image = yield this.provider.fetchTile(zoom, x, y); - const resolution = 256; - const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); - const imageData = context.getImageData(0, 0, resolution, resolution); - const data = imageData.data; - for (let i = 0; i < data.length; i += 4) { - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; - const max = 1667721.6; - const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); - data[i] = color.r * 255; - data[i + 1] = color.g * 255; - data[i + 2] = color.b * 255; - } - context.putImageData(imageData, 0, 0); - return canvas; - }); - } +class HeightDebugProvider extends MapProvider { + constructor(provider) { + super(); + this.fromColor = new Color(0xff0000); + this.toColor = new Color(0x00ff00); + this.provider = provider; + } + fetchTile(zoom, x, y) { + return __awaiter(this, void 0, void 0, function* () { + const image = yield this.provider.fetchTile(zoom, x, y); + const resolution = 256; + const canvas = CanvasUtils.createOffscreenCanvas(resolution, resolution); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0, resolution, resolution, 0, 0, resolution, resolution); + const imageData = context.getImageData(0, 0, resolution, resolution); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const value = (r * 65536 + g * 256 + b) * 0.1 - 1e4; + const max = 1667721.6; + const color = this.fromColor.clone().lerpHSL(this.toColor, value / max); + data[i] = color.r * 255; + data[i + 1] = color.g * 255; + data[i + 2] = color.b * 255; + } + context.putImageData(imageData, 0, 0); + return canvas; + }); + } } -class GeolocationUtils { - static get() { - return new Promise(function (resolve, reject) { - navigator.geolocation.getCurrentPosition(function (result) { - resolve(result); - }, reject); - }); - } +class GeolocationUtils { + static get() { + return new Promise(function (resolve, reject) { + navigator.geolocation.getCurrentPosition(function (result) { + resolve(result); + }, reject); + }); + } } -class CancelablePromise { - constructor(executor) { - this.fulfilled = false; - this.rejected = false; - this.called = false; - const resolve = (v) => { - this.fulfilled = true; - this.value = v; - if (typeof this.onResolve === 'function') { - this.onResolve(this.value); - this.called = true; - } - }; - const reject = (reason) => { - this.rejected = true; - this.value = reason; - if (typeof this.onReject === 'function') { - this.onReject(this.value); - this.called = true; - } - }; - try { - executor(resolve, reject); - } - catch (error) { - reject(error); - } - } - cancel() { - return false; - } - then(callback) { - this.onResolve = callback; - if (this.fulfilled && !this.called) { - this.called = true; - this.onResolve(this.value); - } - return this; - } - catch(callback) { - this.onReject = callback; - if (this.rejected && !this.called) { - this.called = true; - this.onReject(this.value); - } - return this; - } - finally(callback) { - return this; - } - static resolve(val) { - return new CancelablePromise(function executor(resolve, _reject) { - resolve(val); - }); - } - static reject(reason) { - return new CancelablePromise(function executor(resolve, reject) { - reject(reason); - }); - } - static all(promises) { - const fulfilledPromises = []; - const result = []; - function executor(resolve, reject) { - promises.forEach((promise, index) => { - return promise - .then((val) => { - fulfilledPromises.push(true); - result[index] = val; - if (fulfilledPromises.length === promises.length) { - return resolve(result); - } - }) - .catch((error) => { return reject(error); }); - }); - } - return new CancelablePromise(executor); - } +class CancelablePromise { + constructor(executor) { + this.fulfilled = false; + this.rejected = false; + this.called = false; + const resolve = (v) => { + this.fulfilled = true; + this.value = v; + if (typeof this.onResolve === 'function') { + this.onResolve(this.value); + this.called = true; + } + }; + const reject = (reason) => { + this.rejected = true; + this.value = reason; + if (typeof this.onReject === 'function') { + this.onReject(this.value); + this.called = true; + } + }; + try { + executor(resolve, reject); + } + catch (error) { + reject(error); + } + } + cancel() { + return false; + } + then(callback) { + this.onResolve = callback; + if (this.fulfilled && !this.called) { + this.called = true; + this.onResolve(this.value); + } + return this; + } + catch(callback) { + this.onReject = callback; + if (this.rejected && !this.called) { + this.called = true; + this.onReject(this.value); + } + return this; + } + finally(callback) { + return this; + } + static resolve(val) { + return new CancelablePromise(function executor(resolve, _reject) { + resolve(val); + }); + } + static reject(reason) { + return new CancelablePromise(function executor(resolve, reject) { + reject(reason); + }); + } + static all(promises) { + const fulfilledPromises = []; + const result = []; + function executor(resolve, reject) { + promises.forEach((promise, index) => { + return promise + .then((val) => { + fulfilledPromises.push(true); + result[index] = val; + if (fulfilledPromises.length === promises.length) { + return resolve(result); + } + }) + .catch((error) => { return reject(error); }); + }); + } + return new CancelablePromise(executor); + } } export { BingMapsProvider, CancelablePromise, CanvasUtils, DebugProvider, Geolocation, GeolocationUtils, GoogleMapsProvider, HeightDebugProvider, HereMapsProvider, LODFrustum, LODRadial, LODRaycast, MapBoxProvider, MapHeightNode, MapHeightNodeShader, MapNode, MapNodeGeometry, MapNodeHeightGeometry, MapPlaneNode, MapProvider, MapSphereNode, MapSphereNodeGeometry, MapTilerProvider, MapView, OpenMapTilesProvider, OpenStreetMapsProvider, QuadTreePosition, TextureUtils, UnitsUtils, XHRUtils }; diff --git a/build/nodes/MapNode.d.ts b/build/nodes/MapNode.d.ts index af8035f..307a794 100644 --- a/build/nodes/MapNode.d.ts +++ b/build/nodes/MapNode.d.ts @@ -29,7 +29,6 @@ export declare abstract class MapNode extends Mesh { subdivide(): void; simplify(): void; loadData(): Promise; - applyTexture(image: HTMLImageElement): Promise; nodeReady(): void; dispose(): void; } diff --git a/build/nodes/MapSphereNode.d.ts b/build/nodes/MapSphereNode.d.ts index 82abae3..3634afa 100644 --- a/build/nodes/MapSphereNode.d.ts +++ b/build/nodes/MapSphereNode.d.ts @@ -8,7 +8,6 @@ export declare class MapSphereNode extends MapNode { constructor(parentNode?: any, mapView?: any, location?: number, level?: number, x?: number, y?: number); initialize(): Promise; static createGeometry(zoom: number, x: number, y: number): MapSphereNodeGeometry; - applyTexture(image: HTMLImageElement): Promise; applyScaleNode(): void; updateMatrix(): void; updateMatrixWorld(force?: boolean): void; diff --git a/build/utils/UnitsUtils.d.ts b/build/utils/UnitsUtils.d.ts index 677b984..801ab82 100644 --- a/build/utils/UnitsUtils.d.ts +++ b/build/utils/UnitsUtils.d.ts @@ -6,15 +6,10 @@ export declare class UnitsUtils { static EARTH_RADIUS_B: number; static EARTH_PERIMETER: number; static EARTH_ORIGIN: number; - static WEB_MERCATOR_MAX_EXTENT: number; static datumsToSpherical(latitude: number, longitude: number): Vector2; static sphericalToDatums(x: number, y: number): Geolocation; static quadtreeToDatums(zoom: number, x: number, y: number): Geolocation; static vectorToDatums(dir: Vector3): Geolocation; static datumsToVector(latitude: number, longitude: number): Vector3; static mapboxAltitude(color: Color): number; - static getTileSize(zoom: number): number; - static tileBounds(zoom: number, x: number, y: number): number[]; - static webMercatorToLatitude(zoom: number, y: number): number; - static webMercatorToLongitude(zoom: number, x: number): number; } diff --git a/examples/basic.js b/examples/basic.js index bb5fe67..3ba2d2b 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -30611,376 +30611,6 @@ } - const Cache = { - - enabled: false, - - files: {}, - - add: function ( key, file ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Adding key:', key ); - - this.files[ key ] = file; - - }, - - get: function ( key ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Checking key:', key ); - - return this.files[ key ]; - - }, - - remove: function ( key ) { - - delete this.files[ key ]; - - }, - - clear: function () { - - this.files = {}; - - } - - }; - - class LoadingManager { - - constructor( onLoad, onProgress, onError ) { - - const scope = this; - - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; - - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor - - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; - - this.itemStart = function ( url ) { - - itemsTotal ++; - - if ( isLoading === false ) { - - if ( scope.onStart !== undefined ) { - - scope.onStart( url, itemsLoaded, itemsTotal ); - - } - - } - - isLoading = true; - - }; - - this.itemEnd = function ( url ) { - - itemsLoaded ++; - - if ( scope.onProgress !== undefined ) { - - scope.onProgress( url, itemsLoaded, itemsTotal ); - - } - - if ( itemsLoaded === itemsTotal ) { - - isLoading = false; - - if ( scope.onLoad !== undefined ) { - - scope.onLoad(); - - } - - } - - }; - - this.itemError = function ( url ) { - - if ( scope.onError !== undefined ) { - - scope.onError( url ); - - } - - }; - - this.resolveURL = function ( url ) { - - if ( urlModifier ) { - - return urlModifier( url ); - - } - - return url; - - }; - - this.setURLModifier = function ( transform ) { - - urlModifier = transform; - - return this; - - }; - - this.addHandler = function ( regex, loader ) { - - handlers.push( regex, loader ); - - return this; - - }; - - this.removeHandler = function ( regex ) { - - const index = handlers.indexOf( regex ); - - if ( index !== - 1 ) { - - handlers.splice( index, 2 ); - - } - - return this; - - }; - - this.getHandler = function ( file ) { - - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; - - if ( regex.global ) regex.lastIndex = 0; // see #17920 - - if ( regex.test( file ) ) { - - return loader; - - } - - } - - return null; - - }; - - } - - } - - const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - - class Loader { - - constructor( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; - - } - - load( /* url, onLoad, onProgress, onError */ ) {} - - loadAsync( url, onProgress ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.load( url, resolve, onProgress, reject ); - - } ); - - } - - parse( /* data */ ) {} - - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - setWithCredentials( value ) { - - this.withCredentials = value; - return this; - - } - - setPath( path ) { - - this.path = path; - return this; - - } - - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; - - } - - setRequestHeader( requestHeader ) { - - this.requestHeader = requestHeader; - return this; - - } - - } - - Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; - - class ImageLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const scope = this; - - const cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - const image = createElementNS( 'img' ); - - function onImageLoad() { - - removeEventListeners(); - - Cache.add( url, this ); - - if ( onLoad ) onLoad( this ); - - scope.manager.itemEnd( url ); - - } - - function onImageError( event ) { - - removeEventListeners(); - - if ( onError ) onError( event ); - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } - - function removeEventListeners() { - - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); - - } - - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); - - if ( url.slice( 0, 5 ) !== 'data:' ) { - - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - - } - - scope.manager.itemStart( url ); - - image.src = url; - - return image; - - } - - } - - class TextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const texture = new Texture(); - - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - loader.load( url, function ( image ) { - - texture.image = image; - texture.needsUpdate = true; - - if ( onLoad !== undefined ) { - - onLoad( texture ); - - } - - }, onProgress, onError ); - - return texture; - - } - - } - class Light extends Object3D { constructor( color, intensity = 1 ) { @@ -31728,8 +31358,6 @@ const pointers = []; const pointerPositions = {}; - let controlActive = false; - function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31746,8 +31374,8 @@ function getZoomScale( delta ) { - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); + const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); + return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); } @@ -32444,70 +32072,12 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( customWheelEvent( event ) ); + handleMouseWheel( event ); scope.dispatchEvent( _endEvent ); } - function customWheelEvent( event ) { - - const mode = event.deltaMode; - - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; - - switch ( mode ) { - - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; - - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; - - } - - // detect if event was triggered by pinching - if ( event.ctrlKey && !controlActive ) { - - newEvent.deltaY *= 10; - - } - - return newEvent; - - } - - function interceptControlDown( event ) { - - if ( event.key === "Control" ) { - - controlActive = true; - - document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - - function interceptControlUp( event ) { - - if ( event.key === "Control" ) { - - controlActive = false; - - document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32716,8 +32286,6 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); - // force an update at start this.update(); @@ -32749,29 +32317,29 @@ } - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -32915,7 +32483,16 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; } catch (e) { if (this.disposed) { @@ -32927,23 +32504,6 @@ this.material.needsUpdate = true; }); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new Texture(image); - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -33146,32 +32706,12 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; - UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -33449,48 +32989,7 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new Vector4(...bounds); - const material = new ShaderMaterial({ - uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33510,28 +33009,12 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -33686,11 +33169,7 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } + this.raycaster.intersectObjects(view.children, true, intersects); } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; diff --git a/examples/providers.js b/examples/providers.js index 06dee81..8e12898 100644 --- a/examples/providers.js +++ b/examples/providers.js @@ -30611,376 +30611,6 @@ } - const Cache = { - - enabled: false, - - files: {}, - - add: function ( key, file ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Adding key:', key ); - - this.files[ key ] = file; - - }, - - get: function ( key ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Checking key:', key ); - - return this.files[ key ]; - - }, - - remove: function ( key ) { - - delete this.files[ key ]; - - }, - - clear: function () { - - this.files = {}; - - } - - }; - - class LoadingManager { - - constructor( onLoad, onProgress, onError ) { - - const scope = this; - - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; - - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor - - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; - - this.itemStart = function ( url ) { - - itemsTotal ++; - - if ( isLoading === false ) { - - if ( scope.onStart !== undefined ) { - - scope.onStart( url, itemsLoaded, itemsTotal ); - - } - - } - - isLoading = true; - - }; - - this.itemEnd = function ( url ) { - - itemsLoaded ++; - - if ( scope.onProgress !== undefined ) { - - scope.onProgress( url, itemsLoaded, itemsTotal ); - - } - - if ( itemsLoaded === itemsTotal ) { - - isLoading = false; - - if ( scope.onLoad !== undefined ) { - - scope.onLoad(); - - } - - } - - }; - - this.itemError = function ( url ) { - - if ( scope.onError !== undefined ) { - - scope.onError( url ); - - } - - }; - - this.resolveURL = function ( url ) { - - if ( urlModifier ) { - - return urlModifier( url ); - - } - - return url; - - }; - - this.setURLModifier = function ( transform ) { - - urlModifier = transform; - - return this; - - }; - - this.addHandler = function ( regex, loader ) { - - handlers.push( regex, loader ); - - return this; - - }; - - this.removeHandler = function ( regex ) { - - const index = handlers.indexOf( regex ); - - if ( index !== - 1 ) { - - handlers.splice( index, 2 ); - - } - - return this; - - }; - - this.getHandler = function ( file ) { - - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; - - if ( regex.global ) regex.lastIndex = 0; // see #17920 - - if ( regex.test( file ) ) { - - return loader; - - } - - } - - return null; - - }; - - } - - } - - const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - - class Loader { - - constructor( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; - - } - - load( /* url, onLoad, onProgress, onError */ ) {} - - loadAsync( url, onProgress ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.load( url, resolve, onProgress, reject ); - - } ); - - } - - parse( /* data */ ) {} - - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - setWithCredentials( value ) { - - this.withCredentials = value; - return this; - - } - - setPath( path ) { - - this.path = path; - return this; - - } - - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; - - } - - setRequestHeader( requestHeader ) { - - this.requestHeader = requestHeader; - return this; - - } - - } - - Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; - - class ImageLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const scope = this; - - const cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - const image = createElementNS( 'img' ); - - function onImageLoad() { - - removeEventListeners(); - - Cache.add( url, this ); - - if ( onLoad ) onLoad( this ); - - scope.manager.itemEnd( url ); - - } - - function onImageError( event ) { - - removeEventListeners(); - - if ( onError ) onError( event ); - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } - - function removeEventListeners() { - - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); - - } - - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); - - if ( url.slice( 0, 5 ) !== 'data:' ) { - - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - - } - - scope.manager.itemStart( url ); - - image.src = url; - - return image; - - } - - } - - class TextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const texture = new Texture(); - - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - loader.load( url, function ( image ) { - - texture.image = image; - texture.needsUpdate = true; - - if ( onLoad !== undefined ) { - - onLoad( texture ); - - } - - }, onProgress, onError ); - - return texture; - - } - - } - class Light extends Object3D { constructor( color, intensity = 1 ) { @@ -31918,8 +31548,6 @@ const pointers = []; const pointerPositions = {}; - let controlActive = false; - function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31936,8 +31564,8 @@ function getZoomScale( delta ) { - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); + const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); + return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); } @@ -32634,70 +32262,12 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( customWheelEvent( event ) ); + handleMouseWheel( event ); scope.dispatchEvent( _endEvent ); } - function customWheelEvent( event ) { - - const mode = event.deltaMode; - - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; - - switch ( mode ) { - - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; - - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; - - } - - // detect if event was triggered by pinching - if ( event.ctrlKey && !controlActive ) { - - newEvent.deltaY *= 10; - - } - - return newEvent; - - } - - function interceptControlDown( event ) { - - if ( event.key === "Control" ) { - - controlActive = true; - - document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - - function interceptControlUp( event ) { - - if ( event.key === "Control" ) { - - controlActive = false; - - document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32906,8 +32476,6 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); - // force an update at start this.update(); @@ -33148,29 +32716,29 @@ }; - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -33314,7 +32882,16 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; } catch (e) { if (this.disposed) { @@ -33326,23 +32903,6 @@ this.material.needsUpdate = true; }); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new Texture(image); - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -33545,32 +33105,12 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; - UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -33848,48 +33388,7 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new Vector4(...bounds); - const material = new ShaderMaterial({ - uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33909,28 +33408,12 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -34085,11 +33568,7 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } + this.raycaster.intersectObjects(view.children, true, intersects); } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; diff --git a/examples/transition.js b/examples/transition.js index d53a83c..444b6d6 100644 --- a/examples/transition.js +++ b/examples/transition.js @@ -31860,8 +31860,6 @@ const pointers = []; const pointerPositions = {}; - let controlActive = false; - function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { @@ -31878,8 +31876,8 @@ function getZoomScale( delta ) { - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); + const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); + return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); } @@ -32576,70 +32574,12 @@ scope.dispatchEvent( _startEvent ); - handleMouseWheel( customWheelEvent( event ) ); + handleMouseWheel( event ); scope.dispatchEvent( _endEvent ); } - function customWheelEvent( event ) { - - const mode = event.deltaMode; - - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; - - switch ( mode ) { - - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; - - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; - - } - - // detect if event was triggered by pinching - if ( event.ctrlKey && !controlActive ) { - - newEvent.deltaY *= 10; - - } - - return newEvent; - - } - - function interceptControlDown( event ) { - - if ( event.key === "Control" ) { - - controlActive = true; - - document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - - function interceptControlUp( event ) { - - if ( event.key === "Control" ) { - - controlActive = false; - - document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true }); - - } - - } - function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; @@ -32848,8 +32788,6 @@ scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); - // force an update at start this.update(); @@ -32881,29 +32819,29 @@ } - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); } class MapProvider { @@ -33047,7 +32985,16 @@ } try { const image = yield this.mapView.provider.fetchTile(this.level, this.x, this.y); - yield this.applyTexture(image); + if (this.disposed) { + return; + } + const texture = new Texture(image); + texture.generateMipmaps = false; + texture.format = RGBAFormat; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.needsUpdate = true; + this.material.map = texture; } catch (e) { if (this.disposed) { @@ -33059,23 +33006,6 @@ this.material.needsUpdate = true; }); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - if (this.disposed) { - return; - } - const texture = new Texture(image); - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - texture.generateMipmaps = false; - texture.format = RGBAFormat; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.needsUpdate = true; - this.material.map = texture; - }); - } nodeReady() { if (this.disposed) { console.warn('Geo-Three: nodeReady() called for disposed node.', this); @@ -33278,32 +33208,12 @@ static mapboxAltitude(color) { return (color.r * 255.0 * 65536.0 + color.g * 255.0 * 256.0 + color.b * 255.0) * 0.1 - 10000.0; } - static getTileSize(zoom) { - const maxExtent = UnitsUtils.WEB_MERCATOR_MAX_EXTENT; - const numTiles = Math.pow(2, zoom); - return 2 * maxExtent / numTiles; - } - static tileBounds(zoom, x, y) { - const tileSize = UnitsUtils.getTileSize(zoom); - const minX = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * tileSize; - const minY = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - (y + 1) * tileSize; - return [minX, tileSize, minY, tileSize]; - } - static webMercatorToLatitude(zoom, y) { - const yMerc = UnitsUtils.WEB_MERCATOR_MAX_EXTENT - y * UnitsUtils.getTileSize(zoom); - return Math.atan(Math.sinh(yMerc / UnitsUtils.EARTH_RADIUS)); - } - static webMercatorToLongitude(zoom, x) { - const xMerc = -UnitsUtils.WEB_MERCATOR_MAX_EXTENT + x * UnitsUtils.getTileSize(zoom); - return xMerc / UnitsUtils.EARTH_RADIUS; - } } UnitsUtils.EARTH_RADIUS = 6371008; UnitsUtils.EARTH_RADIUS_A = 6378137.0; UnitsUtils.EARTH_RADIUS_B = 6356752.314245; UnitsUtils.EARTH_PERIMETER = 2 * Math.PI * UnitsUtils.EARTH_RADIUS; UnitsUtils.EARTH_ORIGIN = UnitsUtils.EARTH_PERIMETER / 2.0; - UnitsUtils.WEB_MERCATOR_MAX_EXTENT = 20037508.34; class MapPlaneNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { @@ -33581,48 +33491,7 @@ class MapSphereNode extends MapNode { constructor(parentNode = null, mapView = null, location = QuadTreePosition.root, level = 0, x = 0, y = 0) { - let bounds = UnitsUtils.tileBounds(level, x, y); - const vertexShader = ` - varying vec3 vPosition; - - void main() { - vPosition = position; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - const fragmentShader = ` - #define PI 3.1415926538 - varying vec3 vPosition; - uniform sampler2D uTexture; - uniform vec4 webMercatorBounds; - - void main() { - // this could also be a constant, but for some reason using a constant causes more visible tile gaps at high zoom - float radius = length(vPosition); - - float latitude = asin(vPosition.y / radius); - float longitude = atan(-vPosition.z, vPosition.x); - - float web_mercator_x = radius * longitude; - float web_mercator_y = radius * log(tan(PI / 4.0 + latitude / 2.0)); - float y = (web_mercator_y - webMercatorBounds.z) / webMercatorBounds.w; - float x = (web_mercator_x - webMercatorBounds.x) / webMercatorBounds.y; - - vec4 color = texture2D(uTexture, vec2(x, y)); - gl_FragColor = color; - ${parseInt(REVISION) < 152 ? '' : ` - #include - #include ${(parseInt(REVISION) >= 154) ? '' : ''} - `} - } - `; - let vBounds = new Vector4(...bounds); - const material = new ShaderMaterial({ - uniforms: { uTexture: { value: new Texture() }, webMercatorBounds: { value: vBounds } }, - vertexShader: vertexShader, - fragmentShader: fragmentShader - }); - super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), material); + super(parentNode, mapView, location, level, x, y, MapSphereNode.createGeometry(level, x, y), new MeshBasicMaterial({ wireframe: false })); this.applyScaleNode(); this.matrixAutoUpdate = false; this.isMesh = true; @@ -33642,28 +33511,12 @@ const range = Math.pow(2, zoom); const max = 40; const segments = Math.floor(MapSphereNode.segments * (max / (zoom + 1)) / max); - const lon1 = x > 0 ? UnitsUtils.webMercatorToLongitude(zoom, x) + Math.PI : 0; - const lon2 = x < range - 1 ? UnitsUtils.webMercatorToLongitude(zoom, x + 1) + Math.PI : 2 * Math.PI; - const phiStart = lon1; - const phiLength = lon2 - lon1; - const lat1 = y > 0 ? UnitsUtils.webMercatorToLatitude(zoom, y) : Math.PI / 2; - const lat2 = y < range - 1 ? UnitsUtils.webMercatorToLatitude(zoom, y + 1) : -Math.PI / 2; - const thetaLength = lat1 - lat2; - const thetaStart = Math.PI - (lat1 + Math.PI / 2); + const phiLength = 1 / range * 2 * Math.PI; + const phiStart = x * phiLength; + const thetaLength = 1 / range * Math.PI; + const thetaStart = y * thetaLength; return new MapSphereNodeGeometry(1, segments, segments, phiStart, phiLength, thetaStart, thetaLength); } - applyTexture(image) { - return __awaiter(this, void 0, void 0, function* () { - const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function () { - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } - }); - this.material.uniforms.uTexture.value = texture; - this.material.uniforms.uTexture.needsUpdate = true; - }); - } applyScaleNode() { this.geometry.computeBoundingBox(); const box = this.geometry.boundingBox.clone(); @@ -33818,11 +33671,7 @@ for (let t = 0; t < this.subdivisionRays; t++) { this.mouse.set(Math.random() * 2 - 1, Math.random() * 2 - 1); this.raycaster.setFromCamera(this.mouse, camera); - let myIntersects = []; - this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { - intersects.push(myIntersects[0]); - } + this.raycaster.intersectObjects(view.children, true, intersects); } for (let i = 0; i < intersects.length; i++) { const node = intersects[i].object; @@ -34601,9 +34450,6 @@ scene.background = new Color(0x000000, LinearSRGBColorSpace); var loader = new TextureLoader(); loader.load('2k_earth_daymap.jpg', function (texture) { - if (parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb'; - } var sphere = new Mesh(new SphereGeometry(UnitsUtils.EARTH_RADIUS, 256, 256), new MeshBasicMaterial({ map: texture })); scene.add(sphere); }); From b19afde762db8a502d95116b9d6ab5b062bc9638 Mon Sep 17 00:00:00 2001 From: rodri Date: Sun, 22 Sep 2024 07:44:37 -0400 Subject: [PATCH 4/5] lint --- source/lod/LODRaycast.ts | 3 ++- source/nodes/MapNode.ts | 5 +++-- source/nodes/MapSphereNode.ts | 14 ++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/source/lod/LODRaycast.ts b/source/lod/LODRaycast.ts index c63108f..dd2c79d 100644 --- a/source/lod/LODRaycast.ts +++ b/source/lod/LODRaycast.ts @@ -66,7 +66,8 @@ export class LODRaycast implements LODControl let myIntersects = []; this.raycaster.intersectObjects(view.children, true, myIntersects); - if (myIntersects.length > 0) { + if (myIntersects.length > 0) + { // Only use first intersection with the terrain intersects.push(myIntersects[0]); } diff --git a/source/nodes/MapNode.ts b/source/nodes/MapNode.ts index 9cda566..8682b4f 100644 --- a/source/nodes/MapNode.ts +++ b/source/nodes/MapNode.ts @@ -286,8 +286,9 @@ export abstract class MapNode extends Mesh } const texture = new Texture(image); - if(parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb' + if (parseInt(REVISION) >= 152) + { + texture.colorSpace = 'srgb'; } texture.generateMipmaps = false; texture.format = RGBAFormat; diff --git a/source/nodes/MapSphereNode.ts b/source/nodes/MapSphereNode.ts index 931f3a0..6fd21db 100644 --- a/source/nodes/MapSphereNode.ts +++ b/source/nodes/MapSphereNode.ts @@ -68,11 +68,11 @@ export class MapSphereNode extends MapNode vec4 color = texture2D(uTexture, vec2(x, y)); gl_FragColor = color; ${ - parseInt(REVISION) < 152 ? '' : ` + parseInt(REVISION) < 152 ? '' : ` #include - #include ${(parseInt(REVISION) >= 154) ? '' : ''} + #include ${parseInt(REVISION) >= 154 ? '' : ''} ` - } +} } `; @@ -133,9 +133,11 @@ export class MapSphereNode extends MapNode public async applyTexture(image: HTMLImageElement): Promise { const textureLoader = new TextureLoader(); - const texture = textureLoader.load(image.src, function() { - if(parseInt(REVISION) >= 152) { - texture.colorSpace = 'srgb' + const texture = textureLoader.load(image.src, function() + { + if (parseInt(REVISION) >= 152) + { + texture.colorSpace = 'srgb'; } }); // @ts-ignore From 93102855f82057e7987110c4f77686e2156846a4 Mon Sep 17 00:00:00 2001 From: rodri Date: Sun, 22 Sep 2024 08:31:06 -0400 Subject: [PATCH 5/5] bump GH Actions Node to 16 --- .github/workflows/build.workflow.yml | 2 +- .github/workflows/lint.workflow.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.workflow.yml b/.github/workflows/build.workflow.yml index 534435a..6b5c695 100644 --- a/.github/workflows/build.workflow.yml +++ b/.github/workflows/build.workflow.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 16 - name: Install depedencies from NPM run: npm install diff --git a/.github/workflows/lint.workflow.yml b/.github/workflows/lint.workflow.yml index 08eeb82..5afb53d 100644 --- a/.github/workflows/lint.workflow.yml +++ b/.github/workflows/lint.workflow.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 16 - name: Install depedencies from NPM run: npm install