readd WebWorldWind

This commit is contained in:
tengge 2020-08-13 19:34:54 +08:00
parent 5ac8c7b988
commit cffa786337
152 changed files with 46891 additions and 1 deletions

240
package-lock.json generated
View File

@ -591,6 +591,16 @@
"color-convert": "^1.9.0"
}
},
"anymatch": {
"version": "3.1.1",
"resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz",
"integrity": "sha1-xV7PAhheJGklk5kxDBc84xIzsUI=",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz",
@ -738,6 +748,12 @@
"integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=",
"dev": true
},
"binary-extensions": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.1.0.tgz",
"integrity": "sha1-MPpAyef+B9vIlWeM0ocCTeokHdk=",
"dev": true
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.7.2.tgz?cache=0&sync_timestamp=1586263933818&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbluebird%2Fdownload%2Fbluebird-3.7.2.tgz",
@ -767,6 +783,15 @@
"concat-map": "0.0.1"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz",
"integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"browserslist": {
"version": "4.12.0",
"resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.12.0.tgz?cache=0&sync_timestamp=1587419799867&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.12.0.tgz",
@ -884,6 +909,22 @@
"integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=",
"dev": true
},
"chokidar": {
"version": "3.4.2",
"resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-3.4.2.tgz",
"integrity": "sha1-ONyOZY3sOAl0HrPve7Ckf+QkIy0=",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.1.2",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.4.0"
}
},
"chromium-pickle-js": {
"version": "0.2.0",
"resolved": "https://registry.npm.taobao.org/chromium-pickle-js/download/chromium-pickle-js-0.2.0.tgz",
@ -2123,6 +2164,15 @@
"flat-cache": "^2.0.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz",
"integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz",
@ -2205,6 +2255,13 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.3.tgz",
"integrity": "sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4=",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz",
@ -2733,6 +2790,15 @@
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz",
"integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-callable": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.1.4.tgz",
@ -2792,6 +2858,12 @@
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
"dev": true
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz",
"integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
"dev": true
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/is-obj/download/is-obj-2.0.0.tgz",
@ -3116,6 +3188,12 @@
"integrity": "sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=",
"dev": true
},
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npm.taobao.org/memorystream/download/memorystream-0.3.1.tgz",
"integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
"dev": true
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/mimic-fn/download/mimic-fn-2.1.0.tgz?cache=0&sync_timestamp=1560442058146&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmimic-fn%2Fdownload%2Fmimic-fn-2.1.0.tgz",
@ -3202,6 +3280,12 @@
"validate-npm-package-license": "^3.0.1"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz",
"integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=",
"dev": true
},
"normalize-url": {
"version": "3.3.0",
"resolved": "https://registry.npm.taobao.org/normalize-url/download/normalize-url-3.3.0.tgz",
@ -3228,6 +3312,63 @@
}
}
},
"npm-run-all": {
"version": "4.1.5",
"resolved": "https://registry.npm.taobao.org/npm-run-all/download/npm-run-all-4.1.5.tgz",
"integrity": "sha1-BEdiAqFe4OLiFAgIYb/xKlHZj7o=",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"chalk": "^2.4.1",
"cross-spawn": "^6.0.5",
"memorystream": "^0.3.1",
"minimatch": "^3.0.4",
"pidtree": "^0.3.0",
"read-pkg": "^3.0.0",
"shell-quote": "^1.6.1",
"string.prototype.padend": "^3.0.0"
},
"dependencies": {
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0",
"strip-bom": "^3.0.0"
}
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/path-type/download/path-type-3.0.0.tgz",
"integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=",
"dev": true,
"requires": {
"pify": "^3.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/read-pkg/download/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"dev": true,
"requires": {
"load-json-file": "^4.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^3.0.0"
}
}
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/nth-check/download/nth-check-1.0.2.tgz",
@ -3669,6 +3810,18 @@
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz",
"integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=",
"dev": true
},
"pidtree": {
"version": "0.3.1",
"resolved": "https://registry.npm.taobao.org/pidtree/download/pidtree-0.3.1.tgz",
"integrity": "sha1-7wmsLMBTPfHzJQzPLE02aw0SEUo=",
"dev": true
},
"pify": {
"version": "5.0.0",
"resolved": "https://registry.npm.taobao.org/pify/download/pify-5.0.0.tgz",
@ -4477,6 +4630,15 @@
"util-deprecate": "~1.0.1"
}
},
"readdirp": {
"version": "3.4.0",
"resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-3.4.0.tgz",
"integrity": "sha1-n9zN+ekVWAVEkiGsZF6DA6tbmto=",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npm.taobao.org/regenerate/download/regenerate-1.4.0.tgz",
@ -5010,6 +5172,12 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"shell-quote": {
"version": "1.7.2",
"resolved": "https://registry.npm.taobao.org/shell-quote/download/shell-quote-1.7.2.tgz",
"integrity": "sha1-Z6fQLHbJ2iT5nSCAj8re0ODgS+I=",
"dev": true
},
"side-channel": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/side-channel/download/side-channel-1.0.2.tgz",
@ -5272,6 +5440,69 @@
}
}
},
"string.prototype.padend": {
"version": "3.1.0",
"resolved": "https://registry.npm.taobao.org/string.prototype.padend/download/string.prototype.padend-3.1.0.tgz?cache=0&sync_timestamp=1576312157572&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring.prototype.padend%2Fdownload%2Fstring.prototype.padend-3.1.0.tgz",
"integrity": "sha1-3Aj1eoAQ3FwVNVAxj2fhOtu3KsM=",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.17.6",
"resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz",
"integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.0",
"is-regex": "^1.1.0",
"object-inspect": "^1.7.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.0",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.2.1.tgz",
"integrity": "sha1-5VzUyc3BiLzvsDs2bHNjI/xciYo=",
"dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/has-symbols/download/has-symbols-1.0.1.tgz",
"integrity": "sha1-n1IUdYpEGWxAbZvXbOv4HsLdMeg=",
"dev": true
},
"is-callable": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz",
"integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=",
"dev": true
},
"is-regex": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.1.tgz?cache=0&sync_timestamp=1596555700840&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-regex%2Fdownload%2Fis-regex-1.1.1.tgz",
"integrity": "sha1-xvmKrMVG9s7FRooHt7FTq1ZKV7k=",
"dev": true,
"requires": {
"has-symbols": "^1.0.1"
}
}
}
},
"string.prototype.trimend": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/string.prototype.trimend/download/string.prototype.trimend-1.0.1.tgz",
@ -5805,6 +6036,15 @@
"integrity": "sha1-zgqgwvPfat+FLvtASng+d8BHV3E=",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz",
"integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/truncate-utf8-bytes/download/truncate-utf8-bytes-1.0.2.tgz",

View File

@ -20,7 +20,9 @@
"eslint": "cd web && eslint src --fix",
"clean": "node scripts/clean.js",
"test": "echo \"Error: no test specified\" && exit 1",
"clear": "npm prune"
"clear": "npm prune",
"build-wind": "cd web/test/WebWorldWind && rollup -c rollup.config.js",
"dev-wind": "cd web/test/WebWorldWind && rollup -c rollup.config.js --watch"
},
"dependencies": {
"@tweenjs/tween.js": "^18.5.0",

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>World Wind</title>
<style>
html,
body,
#canvas {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
}
</style>
<script src="build/WorldWind.js" type="text/javascript"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var map;
window.onload = function () {
map = new WorldWind.WorldWindow("canvas");
map.addLayer(new WorldWind.XYZLayer());
map.addLayer(new WorldWind.AtmosphereLayer());
map.addLayer(new WorldWind.StarFieldLayer());
}
</script>
</body>
</html>

View File

@ -0,0 +1,49 @@
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import strip from 'rollup-plugin-strip-banner';
import bundleWorker from 'rollup-plugin-bundle-worker';
function glsl() {
return {
transform(code, id) {
if (/\.glsl$/.test(id) === false) return;
var transformedCode = 'export default ' + JSON.stringify(
code
.replace(/[ \t]*\/\/.*\n/g, '') // remove //
.replace(/[ \t]*\/\*[\s\S]*?\*\//g, '') // remove /* */
.replace(/\n{2,}/g, '\n') // # \n+ to \n
) + ';';
return {
code: transformedCode,
map: {
mappings: ''
}
};
}
};
}
export default {
input: 'src/WorldWind.js',
output: {
indent: '\t',
format: 'umd',
name: 'WorldWind',
file: './build/WorldWind.js'
},
treeshake: true,
external: [],
plugins: [
bundleWorker(),
glsl(),
resolve(),
commonjs(),
strip()
],
onwarn(warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
rollupWarn(warning);
}
}
};

View File

@ -0,0 +1,408 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports BasicWorldWindowController
*/
import Angle from './geom/Angle';
import DragRecognizer from './gesture/DragRecognizer';
import GestureRecognizer from './gesture/GestureRecognizer';
import Matrix from './geom/Matrix';
import PanRecognizer from './gesture/PanRecognizer';
import PinchRecognizer from './gesture/PinchRecognizer';
import RotationRecognizer from './gesture/RotationRecognizer';
import TiltRecognizer from './gesture/TiltRecognizer';
import Vec2 from './geom/Vec2';
import Vec3 from './geom/Vec3';
import WorldWindowController from './WorldWindowController';
import WWMath from './util/WWMath';
/**
* Constructs a window controller with basic capabilities.
* @alias BasicWorldWindowController
* @constructor
* @augments WorldWindowController
* @classDesc This class provides the default window controller for WorldWind for controlling the globe via user interaction.
* @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
*/
function BasicWorldWindowController(worldWindow) {
WorldWindowController.call(this, worldWindow); // base class checks for a valid worldWindow
// Intentionally not documented.
this.primaryDragRecognizer = new DragRecognizer(this.wwd, null);
this.primaryDragRecognizer.addListener(this);
// Intentionally not documented.
this.secondaryDragRecognizer = new DragRecognizer(this.wwd, null);
this.secondaryDragRecognizer.addListener(this);
this.secondaryDragRecognizer.button = 2; // secondary mouse button
// Intentionally not documented.
this.panRecognizer = new PanRecognizer(this.wwd, null);
this.panRecognizer.addListener(this);
// Intentionally not documented.
this.pinchRecognizer = new PinchRecognizer(this.wwd, null);
this.pinchRecognizer.addListener(this);
// Intentionally not documented.
this.rotationRecognizer = new RotationRecognizer(this.wwd, null);
this.rotationRecognizer.addListener(this);
// Intentionally not documented.
this.tiltRecognizer = new TiltRecognizer(this.wwd, null);
this.tiltRecognizer.addListener(this);
// Establish the dependencies between gesture recognizers. The pan, pinch and rotate gesture may recognize
// simultaneously with each other.
this.panRecognizer.recognizeSimultaneouslyWith(this.pinchRecognizer);
this.panRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
this.pinchRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
// Since the tilt gesture is a subset of the pan gesture, pan will typically recognize before tilt,
// effectively suppressing tilt. Establish a dependency between the other touch gestures and tilt to provide
// tilt an opportunity to recognize.
this.panRecognizer.requireRecognizerToFail(this.tiltRecognizer);
this.pinchRecognizer.requireRecognizerToFail(this.tiltRecognizer);
this.rotationRecognizer.requireRecognizerToFail(this.tiltRecognizer);
// Intentionally not documented.
// this.tapRecognizer = new TapRecognizer(this.wwd, null);
// this.tapRecognizer.addListener(this);
// Intentionally not documented.
// this.clickRecognizer = new ClickRecognizer(this.wwd, null);
// this.clickRecognizer.addListener(this);
// Intentionally not documented.
this.beginPoint = new Vec2(0, 0);
this.lastPoint = new Vec2(0, 0);
this.beginHeading = 0;
this.beginTilt = 0;
this.beginRange = 0;
this.lastRotation = 0;
}
BasicWorldWindowController.prototype = Object.create(WorldWindowController.prototype);
// Intentionally not documented.
BasicWorldWindowController.prototype.onGestureEvent = function (e) {
var handled = WorldWindowController.prototype.onGestureEvent.call(this, e);
if (!handled) {
if (e.type === "wheel") {
handled = true;
this.handleWheelEvent(e);
}
else {
for (var i = 0, len = GestureRecognizer.allRecognizers.length; i < len; i++) {
var recognizer = GestureRecognizer.allRecognizers[i];
if (recognizer.target === this.wwd) {
handled |= recognizer.onGestureEvent(e); // use or-assignment to indicate if any recognizer handled the event
}
}
}
}
return handled;
};
// Intentionally not documented.
BasicWorldWindowController.prototype.gestureStateChanged = function (recognizer) {
if (recognizer === this.primaryDragRecognizer || recognizer === this.panRecognizer) {
this.handlePanOrDrag(recognizer);
}
else if (recognizer === this.secondaryDragRecognizer) {
this.handleSecondaryDrag(recognizer);
}
else if (recognizer === this.pinchRecognizer) {
this.handlePinch(recognizer);
}
else if (recognizer === this.rotationRecognizer) {
this.handleRotation(recognizer);
}
else if (recognizer === this.tiltRecognizer) {
this.handleTilt(recognizer);
}
// else if (recognizer === this.clickRecognizer || recognizer === this.tapRecognizer) {
// this.handleClickOrTap(recognizer);
// }
};
// Intentionally not documented.
// BasicWorldWindowController.prototype.handleClickOrTap = function (recognizer) {
// if (recognizer.state === WorldWind.RECOGNIZED) {
// var pickPoint = this.wwd.canvasCoordinates(recognizer.clientX, recognizer.clientY);
//
// // Identify if the top picked object contains a URL for hyperlinking
// var pickList = this.wwd.pick(pickPoint);
// var topObject = pickList.topPickedObject();
// // If the url object was appended, open the hyperlink
// if (topObject &&
// topObject.userObject &&
// topObject.userObject.userProperties &&
// topObject.userObject.userProperties.url) {
// window.open(topObject.userObject.userProperties.url, "_blank");
// }
// }
// };
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag = function (recognizer) {
if (this.wwd.globe.is2D()) {
this.handlePanOrDrag2D(recognizer);
} else {
this.handlePanOrDrag3D(recognizer);
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag3D = function (recognizer) {
var state = recognizer.state,
tx = recognizer.translationX,
ty = recognizer.translationY;
var navigator = this.wwd.navigator;
if (state === WorldWind.BEGAN) {
this.lastPoint.set(0, 0);
} else if (state === WorldWind.CHANGED) {
// Convert the translation from screen coordinates to arc degrees. Use this navigator's range as a
// metric for converting screen pixels to meters, and use the globe's radius for converting from meters
// to arc degrees.
var canvas = this.wwd.canvas,
globe = this.wwd.globe,
globeRadius = WWMath.max(globe.equatorialRadius, globe.polarRadius),
distance = WWMath.max(1, navigator.range),
metersPerPixel = WWMath.perspectivePixelSize(canvas.clientWidth, canvas.clientHeight, distance),
forwardMeters = (ty - this.lastPoint[1]) * metersPerPixel,
sideMeters = -(tx - this.lastPoint[0]) * metersPerPixel,
forwardDegrees = forwardMeters / globeRadius * Angle.RADIANS_TO_DEGREES,
sideDegrees = sideMeters / globeRadius * Angle.RADIANS_TO_DEGREES;
// Apply the change in latitude and longitude to this navigator, relative to the current heading.
var sinHeading = Math.sin(navigator.heading * Angle.DEGREES_TO_RADIANS),
cosHeading = Math.cos(navigator.heading * Angle.DEGREES_TO_RADIANS);
navigator.lookAtLocation.latitude += forwardDegrees * cosHeading - sideDegrees * sinHeading;
navigator.lookAtLocation.longitude += forwardDegrees * sinHeading + sideDegrees * cosHeading;
this.lastPoint.set(tx, ty);
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag2D = function (recognizer) {
var state = recognizer.state,
x = recognizer.clientX,
y = recognizer.clientY,
tx = recognizer.translationX,
ty = recognizer.translationY;
var navigator = this.wwd.navigator;
if (state === WorldWind.BEGAN) {
this.beginPoint.set(x, y);
this.lastPoint.set(x, y);
} else if (state === WorldWind.CHANGED) {
var x1 = this.lastPoint[0],
y1 = this.lastPoint[1],
x2 = this.beginPoint[0] + tx,
y2 = this.beginPoint[1] + ty;
this.lastPoint.set(x2, y2);
var globe = this.wwd.globe,
ray = this.wwd.rayThroughScreenPoint(this.wwd.canvasCoordinates(x1, y1)),
point1 = new Vec3(0, 0, 0),
point2 = new Vec3(0, 0, 0),
origin = new Vec3(0, 0, 0);
if (!globe.intersectsLine(ray, point1)) {
return;
}
ray = this.wwd.rayThroughScreenPoint(this.wwd.canvasCoordinates(x2, y2));
if (!globe.intersectsLine(ray, point2)) {
return;
}
// Transform the original navigator state's modelview matrix to account for the gesture's change.
var modelview = Matrix.fromIdentity();
this.wwd.computeViewingTransform(null, modelview);
modelview.multiplyByTranslation(point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]);
// Compute the globe point at the screen center from the perspective of the transformed navigator state.
modelview.extractEyePoint(ray.origin);
modelview.extractForwardVector(ray.direction);
if (!globe.intersectsLine(ray, origin)) {
return;
}
// Convert the transformed modelview matrix to a set of navigator properties, then apply those
// properties to this navigator.
var params = modelview.extractViewingParameters(origin, navigator.roll, globe, {});
navigator.lookAtLocation.copy(params.origin);
navigator.range = params.range;
navigator.heading = params.heading;
navigator.tilt = params.tilt;
navigator.roll = params.roll;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleSecondaryDrag = function (recognizer) {
var state = recognizer.state,
tx = recognizer.translationX,
ty = recognizer.translationY;
var navigator = this.wwd.navigator;
if (state === WorldWind.BEGAN) {
this.beginHeading = navigator.heading;
this.beginTilt = navigator.tilt;
} else if (state === WorldWind.CHANGED) {
// Compute the current translation from screen coordinates to degrees. Use the canvas dimensions as a
// metric for converting the gesture translation to a fraction of an angle.
var headingDegrees = 180 * tx / this.wwd.canvas.clientWidth,
tiltDegrees = 90 * ty / this.wwd.canvas.clientHeight;
// Apply the change in heading and tilt to this navigator's corresponding properties.
navigator.heading = this.beginHeading + headingDegrees;
navigator.tilt = this.beginTilt - tiltDegrees;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePinch = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
scale = recognizer.scale;
if (state === WorldWind.BEGAN) {
this.beginRange = navigator.range;
} else if (state === WorldWind.CHANGED) {
if (scale !== 0) {
// Apply the change in pinch scale to this navigator's range, relative to the range when the gesture
// began.
navigator.range = this.beginRange / scale;
this.applyLimits();
this.wwd.redraw();
}
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleRotation = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
rotation = recognizer.rotation;
if (state === WorldWind.BEGAN) {
this.lastRotation = 0;
} else if (state === WorldWind.CHANGED) {
// Apply the change in gesture rotation to this navigator's current heading. We apply relative to the
// current heading rather than the heading when the gesture began in order to work simultaneously with
// pan operations that also modify the current heading.
navigator.heading -= rotation - this.lastRotation;
this.lastRotation = rotation;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleTilt = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
ty = recognizer.translationY;
if (state === WorldWind.BEGAN) {
this.beginTilt = navigator.tilt;
} else if (state === WorldWind.CHANGED) {
// Compute the gesture translation from screen coordinates to degrees. Use the canvas dimensions as a
// metric for converting the translation to a fraction of an angle.
var tiltDegrees = -90 * ty / this.wwd.canvas.clientHeight;
// Apply the change in heading and tilt to this navigator's corresponding properties.
navigator.tilt = this.beginTilt + tiltDegrees;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleWheelEvent = function (event) {
var navigator = this.wwd.navigator;
// Normalize the wheel delta based on the wheel delta mode. This produces a roughly consistent delta across
// browsers and input devices.
var normalizedDelta;
if (event.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
normalizedDelta = event.deltaY;
} else if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
normalizedDelta = event.deltaY * 40;
} else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
normalizedDelta = event.deltaY * 400;
}
// Compute a zoom scale factor by adding a fraction of the normalized delta to 1. When multiplied by the
// navigator's range, this has the effect of zooming out or zooming in depending on whether the delta is
// positive or negative, respectfully.
var scale = 1 + normalizedDelta / 1000;
// Apply the scale to this navigator's properties.
navigator.range *= scale;
this.applyLimits();
this.wwd.redraw();
};
// Documented in super-class.
BasicWorldWindowController.prototype.applyLimits = function () {
var navigator = this.wwd.navigator;
// Clamp latitude to between -90 and +90, and normalize longitude to between -180 and +180.
navigator.lookAtLocation.latitude = WWMath.clamp(navigator.lookAtLocation.latitude, -90, 90);
navigator.lookAtLocation.longitude = Angle.normalizedDegreesLongitude(navigator.lookAtLocation.longitude);
// Clamp range to values greater than 1 in order to prevent degenerating to a first-person navigator when
// range is zero.
navigator.range = WWMath.clamp(navigator.range, 1, Number.MAX_VALUE);
// Normalize heading to between -180 and +180.
navigator.heading = Angle.normalizedDegrees(navigator.heading);
// Clamp tilt to between 0 and +90 to prevent the viewer from going upside down.
navigator.tilt = WWMath.clamp(navigator.tilt, 0, 90);
// Normalize heading to between -180 and +180.
navigator.roll = Angle.normalizedDegrees(navigator.roll);
// Apply 2D limits when the globe is 2D.
if (this.wwd.globe.is2D() && navigator.enable2DLimits) {
// Clamp range to prevent more than 360 degrees of visible longitude. Assumes a 45 degree horizontal
// field of view.
var maxRange = 2 * Math.PI * this.wwd.globe.equatorialRadius;
navigator.range = WWMath.clamp(navigator.range, 1, maxRange);
// Force tilt to 0 when in 2D mode to keep the viewer looking straight down.
navigator.tilt = 0;
}
};
export default BasicWorldWindowController;

View File

@ -0,0 +1,7 @@
# WebWorldWind
This is a project from: https://github.com/NASAWorldWind/WebWorldWind
# License
Apache

View File

@ -0,0 +1,517 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import AbstractError from './error/AbstractError';
import Annotation from './shapes/Annotation';
import AnnotationAttributes from './shapes/AnnotationAttributes';
import ArgumentError from './error/ArgumentError';
import AsterV2ElevationCoverage from './globe/AsterV2ElevationCoverage';
import AtmosphereLayer from './layer/AtmosphereLayer';
import AtmosphereProgram from './shaders/AtmosphereProgram';
import BasicProgram from './shaders/BasicProgram';
import BasicTextureProgram from './shaders/BasicTextureProgram';
import BasicWorldWindowController from './BasicWorldWindowController';
import BoundingBox from './geom/BoundingBox';
import ClickRecognizer from './gesture/ClickRecognizer';
import Color from './util/Color';
import Compass from './shapes/Compass';
import DragRecognizer from './gesture/DragRecognizer';
import DrawContext from './render/DrawContext';
import EarthElevationModel from './globe/EarthElevationModel';
import ElevationCoverage from './globe/ElevationCoverage';
import ElevationModel from './globe/ElevationModel';
import Font from './util/Font';
import FrameStatistics from './util/FrameStatistics';
import FramebufferTexture from './render/FramebufferTexture';
import FramebufferTile from './render/FramebufferTile';
import FramebufferTileController from './render/FramebufferTileController';
import Frustum from './geom/Frustum';
import GebcoElevationCoverage from './globe/GebcoElevationCoverage';
import GeographicMesh from './shapes/GeographicMesh';
import GeographicProjection from './projections/GeographicProjection';
import GeographicText from './shapes/GeographicText';
import GestureRecognizer from './gesture/GestureRecognizer';
import Globe from './globe/Globe';
import GpuProgram from './shaders/GpuProgram';
import GpuResourceCache from './cache/GpuResourceCache';
import GpuShader from './shaders/GpuShader';
import GroundProgram from './shaders/GroundProgram';
import HashMap from './util/HashMap';
import ImageSource from './util/ImageSource';
import ImageTile from './render/ImageTile';
import Insets from './util/Insets';
import Layer from './layer/Layer';
import Level from './util/Level';
import LevelSet from './util/LevelSet';
import Line from './geom/Line';
import Location from './geom/Location';
import LookAtNavigator from './navigate/LookAtNavigator';
import Matrix from './geom/Matrix';
import MemoryCache from './cache/MemoryCache';
import MemoryCacheListener from './cache/MemoryCacheListener';
import MercatorTiledImageLayer from './layer/MercatorTiledImageLayer';
import Navigator from './navigate/Navigator';
import NotYetImplementedError from './error/NotYetImplementedError';
import Offset from './util/Offset';
import PanRecognizer from './gesture/PanRecognizer';
import Path from './shapes/Path';
import PickedObject from './pick/PickedObject';
import PickedObjectList from './pick/PickedObjectList';
import PinchRecognizer from './gesture/PinchRecognizer';
import Placemark from './shapes/Placemark';
import PlacemarkAttributes from './shapes/PlacemarkAttributes';
import Plane from './geom/Plane';
import Polygon from './shapes/Polygon';
import Position from './geom/Position';
import ProjectionWgs84 from './projections/ProjectionWgs84';
import Rectangle from './geom/Rectangle';
import Renderable from './render/Renderable';
import RotationRecognizer from './gesture/RotationRecognizer';
import ScreenImage from './shapes/ScreenImage';
import ScreenText from './shapes/ScreenText';
import Sector from './geom/Sector';
import ShapeAttributes from './shapes/ShapeAttributes';
import SkyProgram from './shaders/SkyProgram';
import StarFieldLayer from './layer/StarFieldLayer';
import StarFieldProgram from './shaders/StarFieldProgram';
import SurfaceImage from './shapes/SurfaceImage';
import SurfaceCircle from './shapes/SurfaceCircle';
import SurfaceEllipse from './shapes/SurfaceEllipse';
import SurfacePolygon from './shapes/SurfacePolygon';
import SurfacePolyline from './shapes/SurfacePolyline';
import SurfaceRectangle from './shapes/SurfaceRectangle';
import SurfaceRenderable from './render/SurfaceRenderable';
import SurfaceSector from './shapes/SurfaceSector';
import SurfaceShape from './shapes/SurfaceShape';
import SurfaceShapeTile from './shapes/SurfaceShapeTile';
import SurfaceShapeTileBuilder from './shapes/SurfaceShapeTileBuilder';
import SurfaceTile from './render/SurfaceTile';
import SurfaceTileRenderer from './render/SurfaceTileRenderer';
import SurfaceTileRendererProgram from './shaders/SurfaceTileRendererProgram';
import TapRecognizer from './gesture/TapRecognizer';
import Terrain from './globe/Terrain';
import TerrainTile from './globe/TerrainTile';
import TerrainTileList from './globe/TerrainTileList';
import Tessellator from './globe/Tessellator';
import Text from './shapes/Text';
import TextAttributes from './shapes/TextAttributes';
import TextRenderer from './render/TextRenderer';
import Texture from './render/Texture';
import TextureTile from './render/TextureTile';
import Tile from './util/Tile';
import TiledElevationCoverage from './globe/TiledElevationCoverage';
import TiledImageLayer from './layer/TiledImageLayer';
import TileFactory from './util/TileFactory';
import TiltRecognizer from './gesture/TiltRecognizer';
import Touch from './gesture/Touch';
import TriangleMesh from './shapes/TriangleMesh';
import UsgsNedElevationCoverage from './error/UnsupportedOperationError';
import UsgsNedHiElevationCoverage from './globe/UsgsNedElevationCoverage';
import UnsupportedOperationError from './globe/UsgsNedHiElevationCoverage';
import UrlBuilder from './util/UrlBuilder';
import Vec2 from './geom/Vec2';
import Vec3 from './geom/Vec3';
import WmsUrlBuilder from './util/WmsUrlBuilder';
import WorldWindow from './WorldWindow';
import WorldWindowController from './WorldWindowController';
import WWUtil from './util/WWUtil';
import XYZLayer from './layer/XYZLayer';
import WWMath from './util/WWMath';
/**
* This is the top-level WorldWind module. It is global.
* @exports WorldWind
* @global
*/
var WorldWind = {
/**
* The WorldWind version number.
* @default "0.9.0"
* @constant
*/
VERSION: "0.9.0",
// PLEASE KEEP THE ENTRIES BELOW IN ALPHABETICAL ORDER
/**
* Indicates an altitude mode relative to the globe's ellipsoid.
* @constant
*/
ABSOLUTE: "absolute",
/**
* Indicates that a redraw callback has been called immediately after a redraw.
* @constant
*/
AFTER_REDRAW: "afterRedraw",
/**
* Indicates that a redraw callback has been called immediately before a redraw.
* @constant
*/
BEFORE_REDRAW: "beforeRedraw",
/**
* The BEGAN gesture recognizer state. Continuous gesture recognizers transition to this state from the
* POSSIBLE state when the gesture is first recognized.
* @constant
*/
BEGAN: "began",
/**
* The CANCELLED gesture recognizer state. Continuous gesture recognizers may transition to this state from
* the BEGAN state or the CHANGED state when the touch events are cancelled.
* @constant
*/
CANCELLED: "cancelled",
/**
* The CHANGED gesture recognizer state. Continuous gesture recognizers transition to this state from the
* BEGAN state or the CHANGED state, whenever an input event indicates a change in the gesture.
* @constant
*/
CHANGED: "changed",
/**
* Indicates an altitude mode always on the terrain.
* @constant
*/
CLAMP_TO_GROUND: "clampToGround",
/**
* The radius of Earth.
* @constant
* @deprecated Use WGS84_SEMI_MAJOR_AXIS instead.
*/
EARTH_RADIUS: 6371e3,
/**
* Indicates the cardinal direction east.
* @constant
*/
EAST: "east",
/**
* The ENDED gesture recognizer state. Continuous gesture recognizers transition to this state from either
* the BEGAN state or the CHANGED state when the current input no longer represents the gesture.
* @constant
*/
ENDED: "ended",
/**
* The FAILED gesture recognizer state. Gesture recognizers transition to this state from the POSSIBLE state
* when the gesture cannot be recognized given the current input.
* @constant
*/
FAILED: "failed",
/**
* Indicates a linear filter.
* @constant
*/
FILTER_LINEAR: "filter_linear",
/**
* Indicates a nearest neighbor filter.
* @constant
*/
FILTER_NEAREST: "filter_nearest",
/**
* Indicates a great circle path.
* @constant
*/
GREAT_CIRCLE: "greatCircle",
/**
* Indicates a linear, straight line path.
* @constant
*/
LINEAR: "linear",
/**
* Indicates a multi-point shape, typically within a shapefile.
*/
MULTI_POINT: "multiPoint",
/**
* Indicates the cardinal direction north.
* @constant
*/
NORTH: "north",
/**
* Indicates a null shape, typically within a shapefile.
* @constant
*/
NULL: "null",
/**
* Indicates that the associated parameters are fractional values of the virtual rectangle's width or
* height in the range [0, 1], where 0 indicates the rectangle's origin and 1 indicates the corner
* opposite its origin.
* @constant
*/
OFFSET_FRACTION: "fraction",
/**
* Indicates that the associated parameters are in units of pixels relative to the virtual rectangle's
* corner opposite its origin corner.
* @constant
*/
OFFSET_INSET_PIXELS: "insetPixels",
/**
* Indicates that the associated parameters are in units of pixels relative to the virtual rectangle's
* origin.
* @constant
*/
OFFSET_PIXELS: "pixels",
/**
* Indicates a point shape, typically within a shapefile.
*/
POINT: "point",
/**
* Indicates a polyline shape, typically within a shapefile.
*/
POLYLINE: "polyline",
/**
* Indicates a polygon shape, typically within a shapefile.
*/
POLYGON: "polygon",
/**
* The POSSIBLE gesture recognizer state. Gesture recognizers in this state are idle when there is no input
* event to evaluate, or are evaluating input events to determine whether or not to transition into another
* state.
* @constant
*/
POSSIBLE: "possible",
/**
* The RECOGNIZED gesture recognizer state. Discrete gesture recognizers transition to this state from the
* POSSIBLE state when the gesture is recognized.
* @constant
*/
RECOGNIZED: "recognized",
/**
* The event name of WorldWind redraw events.
*/
REDRAW_EVENT_TYPE: "WorldWindRedraw",
/**
* Indicates that the related value is specified relative to the globe.
* @constant
*/
RELATIVE_TO_GLOBE: "relativeToGlobe",
/**
* Indicates an altitude mode relative to the terrain.
* @constant
*/
RELATIVE_TO_GROUND: "relativeToGround",
/**
* Indicates that the related value is specified relative to the plane of the screen.
* @constant
*/
RELATIVE_TO_SCREEN: "relativeToScreen",
/**
* Indicates a rhumb path -- a path of constant bearing.
* @constant
*/
RHUMB_LINE: "rhumbLine",
/**
* Indicates the cardinal direction south.
* @constant
*/
SOUTH: "south",
/**
* Indicates the cardinal direction west.
* @constant
*/
WEST: "west",
/**
* WGS 84 reference value for Earth's semi-major axis: 6378137.0. Taken from NGA.STND.0036_1.0.0_WGS84,
* section 3.4.1.
* @constant
*/
WGS84_SEMI_MAJOR_AXIS: 6378137.0,
/**
* WGS 84 reference value for Earth's inverse flattening: 298.257223563. Taken from
* NGA.STND.0036_1.0.0_WGS84, section 3.4.2.
* @constant
*/
WGS84_INVERSE_FLATTENING: 298.257223563
};
WorldWind['AbstractError'] = AbstractError;
WorldWind['Annotation'] = Annotation;
WorldWind['AnnotationAttributes'] = AnnotationAttributes;
WorldWind['ArgumentError'] = ArgumentError;
WorldWind['AsterV2ElevationCoverage'] = AsterV2ElevationCoverage;
WorldWind['AtmosphereLayer'] = AtmosphereLayer;
WorldWind['AtmosphereProgram'] = AtmosphereProgram;
WorldWind['BasicProgram'] = BasicProgram;
WorldWind['BasicTextureProgram'] = BasicTextureProgram;
WorldWind['BasicWorldWindowController'] = BasicWorldWindowController;
WorldWind['BoundingBox'] = BoundingBox;
WorldWind['ClickRecognizer'] = ClickRecognizer;
WorldWind['Color'] = Color;
WorldWind['Compass'] = Compass;
WorldWind['DragRecognizer'] = DragRecognizer;
WorldWind['DrawContext'] = DrawContext;
WorldWind['EarthElevationModel'] = EarthElevationModel;
WorldWind['ElevationCoverage'] = ElevationCoverage;
WorldWind['ElevationModel'] = ElevationModel;
WorldWind['Font'] = Font;
WorldWind['FrameStatistics'] = FrameStatistics;
WorldWind['FramebufferTexture'] = FramebufferTexture;
WorldWind['FramebufferTile'] = FramebufferTile;
WorldWind['FramebufferTileController'] = FramebufferTileController;
WorldWind['Frustum'] = Frustum;
WorldWind['GebcoElevationCoverage'] = GebcoElevationCoverage;
WorldWind['GeographicMesh'] = GeographicMesh;
WorldWind['GeographicProjection'] = GeographicProjection;
WorldWind['GeographicText'] = GeographicText;
WorldWind['GestureRecognizer'] = GestureRecognizer;
WorldWind['Globe'] = Globe;
WorldWind['GpuProgram'] = GpuProgram;
WorldWind['GpuResourceCache'] = GpuResourceCache;
WorldWind['GpuShader'] = GpuShader;
WorldWind['GroundProgram'] = GroundProgram;
WorldWind['HashMap'] = HashMap;
WorldWind['ImageSource'] = ImageSource;
WorldWind['ImageTile'] = ImageTile;
WorldWind['Insets'] = Insets;
WorldWind['Layer'] = Layer;
WorldWind['Level'] = Level;
WorldWind['LevelSet'] = LevelSet;
WorldWind['Line'] = Line;
WorldWind['Location'] = Location;
WorldWind['LookAtNavigator'] = LookAtNavigator;
WorldWind['Matrix'] = Matrix;
WorldWind['MemoryCache'] = MemoryCache;
WorldWind['MemoryCacheListener'] = MemoryCacheListener;
WorldWind['MercatorTiledImageLayer'] = MercatorTiledImageLayer;
WorldWind['Navigator'] = Navigator;
WorldWind['NotYetImplementedError'] = NotYetImplementedError;
WorldWind['Offset'] = Offset;
WorldWind['PanRecognizer'] = PanRecognizer;
WorldWind['Path'] = Path;
WorldWind['PickedObject'] = PickedObject;
WorldWind['PickedObjectList'] = PickedObjectList;
WorldWind['PinchRecognizer'] = PinchRecognizer;
WorldWind['Placemark'] = Placemark;
WorldWind['PlacemarkAttributes'] = PlacemarkAttributes;
WorldWind['Plane'] = Plane;
WorldWind['Polygon'] = Polygon;
WorldWind['Position'] = Position;
WorldWind['ProjectionWgs84'] = ProjectionWgs84;
WorldWind['Rectangle'] = Rectangle;
WorldWind['Renderable'] = Renderable;
WorldWind['RotationRecognizer'] = RotationRecognizer;
WorldWind['ScreenText'] = ScreenText;
WorldWind['ScreenImage'] = ScreenImage;
WorldWind['Sector'] = Sector;
WorldWind['ShapeAttributes'] = ShapeAttributes;
WorldWind['SkyProgram'] = SkyProgram;
WorldWind['StarFieldLayer'] = StarFieldLayer;
WorldWind['StarFieldProgram'] = StarFieldProgram;
WorldWind['SurfaceImage'] = SurfaceImage;
WorldWind['SurfaceCircle'] = SurfaceCircle;
WorldWind['SurfaceEllipse'] = SurfaceEllipse;
WorldWind['SurfacePolygon'] = SurfacePolygon;
WorldWind['SurfacePolyline'] = SurfacePolyline;
WorldWind['SurfaceRectangle'] = SurfaceRectangle;
WorldWind['SurfaceRenderable'] = SurfaceRenderable;
WorldWind['SurfaceSector'] = SurfaceSector;
WorldWind['SurfaceShape'] = SurfaceShape;
WorldWind['SurfaceShapeTile'] = SurfaceShapeTile;
WorldWind['SurfaceShapeTileBuilder'] = SurfaceShapeTileBuilder;
WorldWind['SurfaceTile'] = SurfaceTile;
WorldWind['SurfaceTileRenderer'] = SurfaceTileRenderer;
WorldWind['SurfaceTileRendererProgram'] = SurfaceTileRendererProgram;
WorldWind['TapRecognizer'] = TapRecognizer;
WorldWind['Terrain'] = Terrain;
WorldWind['TerrainTile'] = TerrainTile;
WorldWind['TerrainTileList'] = TerrainTileList;
WorldWind['Tessellator'] = Tessellator;
WorldWind['Text'] = Text;
WorldWind['TextAttributes'] = TextAttributes;
WorldWind['TextRenderer'] = TextRenderer;
WorldWind['Texture'] = Texture;
WorldWind['TextureTile'] = TextureTile;
WorldWind['Tile'] = Tile;
WorldWind['TiledElevationCoverage'] = TiledElevationCoverage;
WorldWind['TiledImageLayer'] = TiledImageLayer;
WorldWind['TileFactory'] = TileFactory;
WorldWind['TiltRecognizer'] = TiltRecognizer;
WorldWind['Touch'] = Touch;
WorldWind['TriangleMesh'] = TriangleMesh;
WorldWind['UsgsNedElevationCoverage'] = UsgsNedElevationCoverage;
WorldWind['UsgsNedHiElevationCoverage'] = UsgsNedHiElevationCoverage;
WorldWind['UnsupportedOperationError'] = UnsupportedOperationError;
WorldWind['UrlBuilder'] = UrlBuilder;
WorldWind['Vec2'] = Vec2;
WorldWind['Vec3'] = Vec3;
WorldWind['WmsUrlBuilder'] = WmsUrlBuilder;
WorldWind['WorldWindow'] = WorldWindow;
WorldWind['WorldWindowController'] = WorldWindowController;
WorldWind['XYZLayer'] = XYZLayer;
WorldWind['WWMath'] = WWMath;
/**
* Holds configuration parameters for WorldWind. Applications may modify these parameters prior to creating
* their first WorldWind objects. Configuration properties are:
* <ul>
* <li><code>gpuCacheSize</code>: A Number indicating the size in bytes to allocate from GPU memory for
* resources such as textures, GLSL programs and buffer objects. Default is 250e6 (250 MB).</li>
* <li><code>baseUrl</code>: The URL of the directory containing the WorldWind Library and its resources.</li>
* <li><code>layerRetrievalQueueSize</code>: The number of concurrent tile requests allowed per layer. The default is 16.</li>
* <li><code>coverageRetrievalQueueSize</code>: The number of concurrent tile requests allowed per elevation coverage. The default is 16.</li>
* <li><code>bingLogoPlacement</code>: An {@link Offset} to place a Bing logo attribution. The default is a 7px margin inset from the lower right corner of the screen.</li>
* <li><code>bingLogoAlignment</code>: An {@link Offset} to align the Bing logo relative to its placement position. The default is the lower right corner of the logo.</li>
* </ul>
* @type {{gpuCacheSize: number}}
*/
WorldWind.configuration = {
gpuCacheSize: 250e6,
baseUrl: WWUtil.worldwindlibLocation() || WWUtil.currentUrlSansFilePart() + '/',
layerRetrievalQueueSize: 16,
coverageRetrievalQueueSize: 16,
bingLogoPlacement: new Offset(WorldWind.OFFSET_INSET_PIXELS, 7, WorldWind.OFFSET_PIXELS, 7),
bingLogoAlignment: new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 0)
};
/**
* Indicates the Bing Maps key to use when requesting Bing Maps resources.
* @type {String}
* @default null
*/
WorldWind.BingMapsKey = null;
window.WorldWind = WorldWind;
export default WorldWind;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports WorldWindowController
*/
import ArgumentError from './error/ArgumentError';
import Logger from './util/Logger';
import UnsupportedOperationError from './error/UnsupportedOperationError';
/**
* Constructs a root window controller.
* @alias WorldWindowController
* @constructor
* @abstract
* @classDesc This class provides a base window controller with required properties and methods which sub-classes may
* inherit from to create custom window controllers for controlling the globe via user interaction.
* @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
* @throws {ArgumentError} If the specified WorldWindow is null or undefined.
*/
function WorldWindowController(worldWindow) {
if (!worldWindow) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindowController", "constructor", "missingWorldWindow"));
}
/**
* The WorldWindow associated with this controller.
* @type {WorldWindow}
* @readonly
*/
this.wwd = worldWindow;
// Intentionally not documented.
this.allGestureListeners = [];
}
// Intentionally not documented.
WorldWindowController.prototype.onGestureEvent = function (event) {
var handled = false;
for (var i = 0; i < this.allGestureListeners.length && !handled; i++) {
handled |= this.allGestureListeners[i].onGestureEvent(event);
}
return handled;
};
// Intentionally not documented.
WorldWindowController.prototype.gestureStateChanged = function (recognizer) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindowController", "gestureStateChanged", "abstractInvocation"));
};
/**
* Registers a gesture event listener on this controller. Registering event listeners using this function
* enables applications to prevent the controller's default behavior.
*
* Listeners must implement an onGestureEvent method to receive event notifications. The onGestureEvent method will
* receive one parameter containing the information about the gesture event. Returning true from onGestureEvent
* indicates that the event was processed and will prevent any further handling of the event.
*
* When an event occurs, application event listeners are called before WorldWindowController event listeners.
*
* @param listener The function to call when the event occurs.
* @throws {ArgumentError} If any argument is null or undefined.
*/
WorldWindowController.prototype.addGestureListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindowController", "addGestureListener", "missingListener"));
}
this.allGestureListeners.push(listener);
};
/**
* Removes a gesture event listener from this controller. The listener must be the same object passed to
* addGestureListener. Calling removeGestureListener with arguments that do not identify a currently registered
* listener has no effect.
*
* @param listener The listener to remove. Must be the same object passed to addGestureListener.
* @throws {ArgumentError} If any argument is null or undefined.
*/
WorldWindowController.prototype.removeGestureListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindowController", "removeGestureListener", "missingListener"));
}
var index = this.allGestureListeners.indexOf(listener);
if (index !== -1) {
this.allGestureListeners.splice(index, 1); // remove the listener from the list
}
};
/**
* Called by WorldWindow to allow the controller to enforce navigation limits. Implementation is not required by
* sub-classes.
*/
WorldWindowController.prototype.applyLimits = function () {
};
export default WorldWindowController;

View File

@ -0,0 +1,272 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GpuResourceCache
*/
import AbsentResourceList from '../util/AbsentResourceList';
import ArgumentError from '../error/ArgumentError';
import ImageSource from '../util/ImageSource';
import Logger from '../util/Logger';
import MemoryCache from '../cache/MemoryCache';
import Texture from '../render/Texture';
/**
* Constructs a GPU resource cache for a specified size and low-water value.
* @alias GpuResourceCache
* @constructor
* @classdesc Maintains a cache of GPU resources such as textures and GLSL programs.
* Applications typically do not interact with this class unless they create their own shapes.
* @param {Number} capacity The cache capacity, in bytes.
* @param {Number} lowWater The number of bytes to clear the cache to when it exceeds its capacity.
* @throws {ArgumentError} If the specified capacity is undefined, 0 or negative or the low-water value is
* undefined, negative or not less than the capacity.
*/
function GpuResourceCache(capacity, lowWater) {
if (!capacity || capacity < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "constructor",
"Specified cache capacity is undefined, 0 or negative."));
}
if (!lowWater || lowWater < 0 || lowWater >= capacity) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "constructor",
"Specified cache low-water value is undefined, negative or not less than the capacity."));
}
// Private. Holds the actual cache entries.
this.entries = new MemoryCache(capacity, lowWater);
// Private. Counter for generating cache keys.
this.cacheKeyPool = 0;
// Private. List of retrievals currently in progress.
this.currentRetrievals = {};
// Private. Identifies requested resources that whose retrieval failed.
this.absentResourceList = new AbsentResourceList(3, 60e3);
}
Object.defineProperties(GpuResourceCache.prototype, {
/**
* Indicates the capacity of this cache in bytes.
* @type {Number}
* @readonly
* @memberof GpuResourceCache.prototype
*/
capacity: {
get: function () {
return this.entries.capacity;
}
},
/**
* Indicates the low-water value for this cache in bytes, the size this cache is cleared to when it
* exceeds its capacity.
* @type {Number}
* @readonly
* @memberof GpuResourceCache.prototype
*/
lowWater: {
get: function () {
return this.entries.lowWater;
}
},
/**
* Indicates the number of bytes currently used by this cache.
* @type {Number}
* @readonly
* @memberof GpuResourceCache.prototype
*/
usedCapacity: {
get: function () {
return this.entries.usedCapacity;
}
},
/**
* Indicates the number of free bytes in this cache.
* @type {Number}
* @readonly
* @memberof GpuResourceCache.prototype
*/
freeCapacity: {
get: function () {
return this.entries.freeCapacity;
}
}
});
/**
* Creates a cache key unique to this cache, typically for a resource about to be added to this cache.
* @returns {String} The generated cache key.
*/
GpuResourceCache.prototype.generateCacheKey = function () {
return "GpuResourceCache " + ++this.cacheKeyPool;
};
/**
* Adds a specified resource to this cache. Replaces the existing resource for the specified key if the
* cache currently contains a resource for that key.
* @param {String|ImageSource} key The key or image source of the resource to add.
* @param {Object} resource The resource to add to the cache.
* @param {Number} size The resource's size in bytes. Must be greater than 0.
* @throws {ArgumentError} If either the key or resource arguments is null or undefined
* or if the specified size is less than 1.
*/
GpuResourceCache.prototype.putResource = function (key, resource, size) {
if (!key) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource", "missingKey."));
}
if (!resource) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource", "missingResource."));
}
if (!size || size < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource",
"The specified resource size is undefined or less than 1."));
}
var entry = {
resource: resource
};
this.entries.putEntry(key instanceof ImageSource ? key.key : key, entry, size);
};
/**
* Returns the resource associated with a specified key.
* @param {String|ImageSource} key The key or image source of the resource to find.
* @returns {Object} The resource associated with the specified key, or null if the resource is not in
* this cache or the specified key is null or undefined.
*/
GpuResourceCache.prototype.resourceForKey = function (key) {
var entry = key instanceof ImageSource
? this.entries.entryForKey(key.key) : this.entries.entryForKey(key);
var resource = entry ? entry.resource : null;
// This is faster than checking if the resource is a texture using instanceof.
if (resource !== null && typeof resource.clearTexParameters === "function") {
resource.clearTexParameters();
}
return resource;
};
/**
* Sets a resource's aging factor (multiplier).
* @param {String} key The key of the resource to modify. If null or undefined, the resource's cache entry is not modified.
* @param {Number} agingFactor A multiplier applied to the age of the resource.
*/
GpuResourceCache.prototype.setResourceAgingFactor = function (key, agingFactor) {
this.entries.setEntryAgingFactor(key, agingFactor);
};
/**
* Indicates whether a specified resource is in this cache.
* @param {String|ImageSource} key The key or image source of the resource to find.
* @returns {Boolean} true If the resource is in this cache, false if the resource
* is not in this cache or the specified key is null or undefined.
*/
GpuResourceCache.prototype.containsResource = function (key) {
return this.entries.containsKey(key instanceof ImageSource ? key.key : key);
};
/**
* Removes the specified resource from this cache. The cache is not modified if the specified key is null or
* undefined or does not correspond to an entry in the cache.
* @param {String|ImageSource} key The key or image source of the resource to remove.
*/
GpuResourceCache.prototype.removeResource = function (key) {
this.entries.removeEntry(key instanceof ImageSource ? key.key : key);
};
/**
* Removes all resources from this cache.
*/
GpuResourceCache.prototype.clear = function () {
this.entries.clear(false);
};
/**
* Retrieves an image and adds it to this cache when it arrives. If the specified image source is a URL, a
* retrieval request for the image is made and this method returns immediately with a value of null. A redraw
* event is generated when the image subsequently arrives and is added to this cache. If the image source is an
* {@link ImageSource}, the image is used immediately and this method returns the {@link Texture} created and
* cached for the image. No redraw event is generated in this case.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {String|ImageSource} imageSource The image source, either a {@link ImageSource} or a String
* giving the URL of the image.
* @param {GLenum} wrapMode Optional. Specifies the wrap mode of the texture. Defaults to gl.CLAMP_TO_EDGE
* @returns {Texture} The {@link Texture} created for the image if the specified image source is an
* {@link ImageSource}, otherwise null.
*/
GpuResourceCache.prototype.retrieveTexture = function (gl, imageSource, wrapMode) {
if (!imageSource) {
return null;
}
if (imageSource instanceof ImageSource) {
var t = new Texture(gl, imageSource.image, wrapMode);
this.putResource(imageSource.key, t, t.size);
return t;
}
if (this.currentRetrievals[imageSource] || this.absentResourceList.isResourceAbsent(imageSource)) {
return null;
}
var cache = this,
image = new Image();
image.onload = function () {
Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + imageSource);
var texture = new Texture(gl, image, wrapMode);
cache.putResource(imageSource, texture, texture.size);
delete cache.currentRetrievals[imageSource];
cache.absentResourceList.unmarkResourceAbsent(imageSource);
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
window.dispatchEvent(e);
};
image.onerror = function () {
delete cache.currentRetrievals[imageSource];
cache.absentResourceList.markResourceAbsent(imageSource);
Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + imageSource);
};
this.currentRetrievals[imageSource] = imageSource;
image.crossOrigin = 'anonymous';
image.src = imageSource;
return null;
};
export default GpuResourceCache;

View File

@ -0,0 +1,347 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports MemoryCache
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
/**
* Constructs a memory cache of a specified size.
* @alias MemoryCache
* @constructor
* @classdesc Provides a limited-size memory cache of key-value pairs. The meaning of size depends on usage.
* Some instances of this class work in bytes while others work in counts. See the documentation for the
* specific use to determine the size units.
* @param {Number} capacity The cache's capacity.
* @param {Number} lowWater The size to clear the cache to when its capacity is exceeded.
* @throws {ArgumentError} If either the capacity is 0 or negative or the low-water value is greater than
* or equal to the capacity or less than 1.
*/
function MemoryCache(capacity, lowWater) {
if (!capacity || capacity < 1) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "constructor",
"The specified capacity is undefined, zero or negative"));
}
if (!lowWater || lowWater >= capacity || lowWater < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "constructor",
"The specified low-water value is undefined, greater than or equal to the capacity, or less than 1"));
}
// Documented with its property accessor below.
this._capacity = capacity;
// Documented with its property accessor below.
this._lowWater = lowWater;
/**
* The size currently used by this cache.
* @type {Number}
* @readonly
*/
this.usedCapacity = 0;
/**
* The size currently unused by this cache.
* @type {Number}
* @readonly
*/
this.freeCapacity = capacity;
// Private. The cache entries.
this.entries = {};
// Private. The cache listeners.
this.listeners = [];
}
Object.defineProperties(MemoryCache.prototype, {
/**
* The maximum this cache may hold. When the capacity is explicitly set via this property, and the current
* low-water value is greater than the specified capacity, the low-water value is adjusted to be 85% of
* the specified capacity. The specified capacity may not be less than or equal to 0.
* @type {Number}
* @memberof MemoryCache.prototype
*/
capacity: {
get: function () {
return this._capacity;
},
set: function (value) {
if (!value || value < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "capacity",
"Specified cache capacity is undefined, 0 or negative."));
}
var oldCapacity = this._capacity;
this._capacity = value;
if (this._capacity <= this.lowWater) {
this._lowWater = 0.85 * this._capacity;
}
// Trim the cache to the low-water mark if it's less than the old capacity
if (this._capacity < oldCapacity) {
this.makeSpace(0);
}
}
},
/**
* The size to clear this cache to when its capacity is exceeded. It must be less than the current
* capacity and not negative.
* @type {Number}
* @memberof MemoryCache.prototype
*/
lowWater: {
get: function () {
return this._lowWater;
},
set: function (value) {
if (!value || value >= this._capacity || value < 0) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "lowWater",
"Specified cache low-water value is undefined, negative or not less than the current capacity."));
}
this._lowWater = value;
}
}
});
/**
* Returns the entry for a specified key.
* @param {String} key The key of the entry to return.
* @returns {Object} The entry associated with the specified key, or null if the key is not in the cache or
* is null or undefined.
*/
MemoryCache.prototype.entryForKey = function (key) {
if (!key)
return null;
var cacheEntry = this.entries[key];
if (!cacheEntry)
return null;
cacheEntry.lastUsed = Date.now();
return cacheEntry.entry;
};
/**
* Adds a specified entry to this cache.
* @param {String} key The entry's key.
* @param {Object} entry The entry.
* @param {Number} size The entry's size.
* @throws {ArgumentError} If the specified key or entry is null or undefined or the specified size is less
* than 1.
*/
MemoryCache.prototype.putEntry = function (key, entry, size) {
if (!key) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry", "missingKey."));
}
if (!entry) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry", "missingEntry."));
}
if (size < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry",
"The specified entry size is less than 1."));
}
var existing = this.entries[key],
cacheEntry;
if (existing) {
this.removeEntry(key);
}
if (this.usedCapacity + size > this._capacity) {
this.makeSpace(size);
}
this.usedCapacity += size;
this.freeCapacity = this._capacity - this.usedCapacity;
cacheEntry = {
key: key,
entry: entry,
size: size,
lastUsed: Date.now(),
agingFactor: 1 // 1x = normal aging
};
this.entries[key] = cacheEntry;
};
/**
* Removes all resources from this cache.
* @param {Boolean} callListeners If true, the current cache listeners are called for each entry removed.
* If false, the cache listeners are not called.
*/
MemoryCache.prototype.clear = function (callListeners) {
if (callListeners) {
// Remove each entry individually so that the listeners can be called for each entry.
for (var key in this.entries) {
if (this.entries.hasOwnProperty(key)) {
this.removeCacheEntry(key);
}
}
}
this.entries = {};
this.freeCapacity = this._capacity;
this.usedCapacity = 0;
};
/**
* Remove an entry from this cache.
* @param {String} key The key of the entry to remove. If null or undefined, this cache is not modified.
*/
MemoryCache.prototype.removeEntry = function (key) {
if (!key)
return;
var cacheEntry = this.entries[key];
if (cacheEntry) {
this.removeCacheEntry(cacheEntry);
}
};
/**
* Sets an entry's aging factor (multiplier) used to sort the entries for eviction.
* A value of one is normal aging; a value of two invokes 2x aging, causing
* the entry to become twice as old as a normal sibling with the same
* 'last used' timestamp. Setting a value of zero would be a "fountain
* of youth" for an entry as it wouldn't age and thus would sort to the
* bottom of the eviction queue.
* @param {String} key The key of the entry to modify. If null or undefined, the cache entry is not modified.
* @param {Number} agingFactor A multiplier applied to the age of the entry when sorting candidates for eviction.
*
*/
MemoryCache.prototype.setEntryAgingFactor = function (key, agingFactor) {
if (!key)
return;
var cacheEntry = this.entries[key];
if (cacheEntry) {
cacheEntry.agingFactor = agingFactor;
}
};
// Private. Removes a specified entry from this cache.
MemoryCache.prototype.removeCacheEntry = function (cacheEntry) {
// All removal passes through this function.
delete this.entries[cacheEntry.key];
this.usedCapacity -= cacheEntry.size;
this.freeCapacity = this._capacity - this.usedCapacity;
for (var i = 0, len = this.listeners.length; i < len; i++) {
try {
this.listeners[i].entryRemoved(cacheEntry.key, cacheEntry.entry);
} catch (e) {
this.listeners[i].removalError(e, cacheEntry.key, cacheEntry.entry);
}
}
};
/**
* Indicates whether a specified entry is in this cache.
* @param {String} key The key of the entry to search for.
* @returns {Boolean} true if the entry exists, otherwise false.
*/
MemoryCache.prototype.containsKey = function (key) {
return key && this.entries[key];
};
/**
* Adds a cache listener to this cache.
* @param {MemoryCacheListener} listener The listener to add.
* @throws {ArgumentError} If the specified listener is null or undefined or does not implement both the
* entryRemoved and removalError functions.
*/
MemoryCache.prototype.addCacheListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "addCacheListener", "missingListener"));
}
if (typeof listener.entryRemoved !== "function" || typeof listener.removalError !== "function") {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "addCacheListener",
"The specified listener does not implement the required functions."));
}
this.listeners.push(listener);
};
/**
* Removes a cache listener from this cache.
* @param {MemoryCacheListener} listener The listener to remove.
* @throws {ArgumentError} If the specified listener is null or undefined.
*/
MemoryCache.prototype.removeCacheListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "removeCacheListener", "missingListener"));
}
var index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
// Private. Clears this cache to that necessary to contain a specified amount of free space.
MemoryCache.prototype.makeSpace = function (spaceRequired) {
var sortedEntries = [],
now = Date.now();
// Sort the entries from least recently used to most recently used, then remove the least recently used entries
// until the cache capacity reaches the low water and the cache has enough free capacity for the required
// space.
for (var key in this.entries) {
if (this.entries.hasOwnProperty(key)) {
sortedEntries.push(this.entries[key]);
}
}
sortedEntries.sort(function (a, b) {
var aAge = (now - a.lastUsed) * a.agingFactor,
bAge = (now - b.lastUsed) * b.agingFactor;
return bAge - aAge;
});
for (var i = 0, len = sortedEntries.length; i < len; i++) {
if (this.usedCapacity > this._lowWater || this.freeCapacity < spaceRequired) {
this.removeCacheEntry(sortedEntries[i]);
} else {
break;
}
}
};
export default MemoryCache;

View File

@ -0,0 +1,56 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Defines an interface for {@link MemoryCache} listeners.
* @exports MemoryCacheListener
* @interface MemoryCacheListener
*/
import Logger from '../util/Logger';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* @alias MemoryCacheListener
* @constructor
*/
function MemoryCacheListener() {
}
/**
* Called when an entry is removed from the cache.
* Implementers of this interface must implement this function.
* @param {String} key The key of the entry removed.
* @param {Object} entry The entry removed.
*/
MemoryCacheListener.prototype.entryRemoved = function (key, entry) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCacheListener", "entryRemoved", "abstractInvocation"));
};
/**
* Called when an error occurs during entry removal.
* Implementers of this interface must implement this function.
* @param {Object} error The error object describing the error that occurred.
* @param {String} key The key of the entry being removed.
* @param {Object} entry The entry being removed.
*/
MemoryCacheListener.prototype.removalError = function (error, key, entry) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCacheListener", "removalError", "abstractInvocation"));
};
export default MemoryCacheListener;

View File

@ -0,0 +1,53 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports AbstractError
*/
/**
* Constructs an error with a specified name and message.
* @alias AbstractError
* @constructor
* @abstract
* @classdesc Provides an abstract base class for error classes. This class is not meant to be instantiated
* directly but used only by subclasses.
* @param {String} name The error's name.
* @param {String} message The message.
*/
function AbstractError(name, message) {
this.name = name;
this.message = message;
}
/**
* Returns the message and stack trace associated with this error.
* @returns {String} The message and stack trace associated with this error.
*/
AbstractError.prototype.toString = function () {
var str = this.name + ': ' + this.message;
if (this.stack) {
str += '\n' + this.stack.toString();
}
return str;
};
export default AbstractError;

View File

@ -0,0 +1,46 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ArgumentError
*/
import AbstractError from '../error/AbstractError';
/**
* Constructs an argument error with a specified message.
* @alias ArgumentError
* @constructor
* @classdesc Represents an error associated with invalid function arguments.
* @augments AbstractError
* @param {String} message The message.
*/
function ArgumentError(message) {
AbstractError.call(this, "ArgumentError", message);
var stack;
try {
//noinspection ExceptionCaughtLocallyJS
throw new Error();
} catch (e) {
stack = e.stack;
}
this.stack = stack;
}
ArgumentError.prototype = Object.create(AbstractError.prototype);
export default ArgumentError;

View File

@ -0,0 +1,46 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports NotYetImplementedError
*/
import AbstractError from '../error/AbstractError';
/**
* Constructs a not-yet-implemented error with a specified message.
* @alias NotYetImplementedError
* @constructor
* @classdesc Represents an error associated with an operation that is not yet implemented.
* @augments AbstractError
* @param {String} message The message.
*/
function NotYetImplementedError(message) {
AbstractError.call(this, "NotYetImplementedError", message);
var stack;
try {
//noinspection ExceptionCaughtLocallyJS
throw new Error();
} catch (e) {
stack = e.stack;
}
this.stack = stack;
}
NotYetImplementedError.prototype = Object.create(AbstractError.prototype);
export default NotYetImplementedError;

View File

@ -0,0 +1,48 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports UnsupportedOperationError
*/
import AbstractError from '../error/AbstractError';
/**
* Constructs an unsupported-operation error with a specified message.
* @alias UnsupportedOperationError
* @constructor
* @classdesc Represents an error associated with an operation that is not available or should not be invoked.
* Typically raised when an abstract function of an abstract base class is called because a subclass has not
* implemented the function.
* @augments AbstractError
* @param {String} message The message.
*/
function UnsupportedOperationError(message) {
AbstractError.call(this, "UnsupportedOperationError", message);
var stack;
try {
//noinspection ExceptionCaughtLocallyJS
throw new Error();
} catch (e) {
stack = e.stack;
}
this.stack = stack;
}
UnsupportedOperationError.prototype = Object.create(AbstractError.prototype);
export default UnsupportedOperationError;

View File

@ -0,0 +1,216 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides constants and functions for working with angles.
* @exports Angle
*/
var Angle = {
/**
* Conversion factor for degrees to radians.
* @constant
*/
DEGREES_TO_RADIANS: Math.PI / 180.0,
/**
* Conversion factor for radians to degrees.
* @constant
*/
RADIANS_TO_DEGREES: 180.0 / Math.PI,
/**
* 2 pi.
* @constant
*/
TWO_PI: 2 * Math.PI,
/**
* pi / 2
* @constant
*/
HALF_PI: Math.PI / 2,
/**
* Normalizes a specified value to be within the range of [-180, 180] degrees.
* @param {Number} degrees The value to normalize, in degrees.
* @returns {Number} The specified value normalized to [-180, 180] degrees.
*/
normalizedDegrees: function (degrees) {
var angle = degrees % 360;
return angle > 180 ? angle - 360 : angle < -180 ? 360 + angle : angle;
},
/**
* Normalizes a specified value to be within the range of [-90, 90] degrees.
* @param {Number} degrees The value to normalize, in degrees.
* @returns {Number} The specified value normalized to the normal range of latitude.
*/
normalizedDegreesLatitude: function (degrees) {
var lat = degrees % 180;
return lat > 90 ? 180 - lat : lat < -90 ? -180 - lat : lat;
},
/**
* Normalizes a specified value to be within the range of [-180, 180] degrees.
* @param {Number} degrees The value to normalize, in degrees.
* @returns {Number} The specified value normalized to the normal range of longitude.
*/
normalizedDegreesLongitude: function (degrees) {
var lon = degrees % 360;
return lon > 180 ? lon - 360 : lon < -180 ? 360 + lon : lon;
},
/**
* Normalizes a specified value to be within the range of [-Pi, Pi] radians.
* @param {Number} radians The value to normalize, in radians.
* @returns {Number} The specified value normalized to [-Pi, Pi] radians.
*/
normalizedRadians: function (radians) {
var angle = radians % this.TWO_PI;
return angle > Math.PI ? angle - this.TWO_PI : angle < -Math.PI ? this.TWO_PI + angle : angle;
},
/**
* Normalizes a specified value to be within the range of [-Pi/2, Pi/2] radians.
* @param {Number} radians The value to normalize, in radians.
* @returns {Number} The specified value normalized to the normal range of latitude.
*/
normalizedRadiansLatitude: function (radians) {
var lat = radians % Math.PI;
return lat > this.HALF_PI ? Math.PI - lat : lat < -this.HALF_PI ? -Math.PI - lat : lat;
},
/**
* Normalizes a specified value to be within the range of [-Pi, Pi] radians.
* @param {Number} radians The value to normalize, in radians.
* @returns {Number} The specified value normalized to the normal range of longitude.
*/
normalizedRadiansLongitude: function (radians) {
var lon = radians % this.TWO_PI;
return lon > Math.PI ? lon - this.TWO_PI : lon < -Math.PI ? this.TWO_PI + lon : lon;
},
/**
* Indicates whether a specified value is within the normal range of latitude, [-90, 90].
* @param {Number} degrees The value to test, in degrees.
* @returns {Boolean} true if the value is within the normal range of latitude, otherwise false.
*/
isValidLatitude: function (degrees) {
return degrees >= -90 && degrees <= 90;
},
/**
* Indicates whether a specified value is within the normal range of longitude, [-180, 180].
* @param {Number} degrees The value to test, in degrees.
* @returns {boolean} true if the value is within the normal range of longitude, otherwise false.
*/
isValidLongitude: function (degrees) {
return degrees >= -180 && degrees <= 180;
},
/**
* Returns a string representation of a specified value in degrees.
* @param {Number} degrees The value for which to compute the string.
* @returns {String} The computed string, which is a decimal degrees value followed by the degree symbol.
*/
toString: function (degrees) {
return degrees.toString() + '\u00B0';
},
/**
* Returns a decimal degrees string representation of a specified value in degrees.
* @param {Number} degrees The value for which to compute the string.
* @returns {String} The computed string, which is a decimal degrees value followed by the degree symbol.
*/
toDecimalDegreesString: function (degrees) {
return degrees.toString() + '\u00B0';
},
/**
* Returns a degrees-minutes-seconds string representation of a specified value in degrees.
* @param {Number} degrees The value for which to compute the string.
* @returns {String} The computed string in degrees, minutes and decimal seconds.
*/
toDMSString: function (degrees) {
var sign,
temp,
d,
m,
s;
sign = degrees < 0 ? -1 : 1;
temp = sign * degrees;
d = Math.floor(temp);
temp = (temp - d) * 60;
m = Math.floor(temp);
temp = (temp - m) * 60;
s = Math.round(temp);
if (s == 60) {
m++;
s = 0;
}
if (m == 60) {
d++;
m = 0;
}
return (sign == -1 ? "-" : "") + d + "\u00B0" + " " + m + "\u2019" + " " + s + "\u201D";
},
/**
* Returns a degrees-minutes string representation of a specified value in degrees.
* @param {Number} degrees The value for which to compute the string.
* @returns {String} The computed string in degrees and decimal minutes.
*/
toDMString: function (degrees) {
var sign,
temp,
d,
m,
s,
mf;
sign = degrees < 0 ? -1 : 1;
temp = sign * degrees;
d = Math.floor(temp);
temp = (temp - d) * 60;
m = Math.floor(temp);
temp = (temp - m) * 60;
s = Math.round(temp);
if (s == 60) {
m++;
s = 0;
}
if (m == 60) {
d++;
m = 0;
}
mf = s == 0 ? m : m + s / 60;
return (sign == -1 ? "-" : "") + d + "\u00B0" + " " + mf + "\u2019";
}
};
export default Angle;

View File

@ -0,0 +1,579 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports BoundingBox
*/
import ArgumentError from '../error/ArgumentError';
import BasicProgram from '../shaders/BasicProgram';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import Vec3 from '../geom/Vec3';
import WWMath from '../util/WWMath';
import WWUtil from '../util/WWUtil';
/**
* Constructs a unit bounding box.
* The unit box has its R, S and T axes aligned with the X, Y and Z axes, respectively, and has its length,
* width and height set to 1.
* @alias BoundingBox
* @constructor
* @classdesc Represents a bounding box in Cartesian coordinates. Typically used as a bounding volume.
*/
function BoundingBox() {
/**
* The box's center point.
* @type {Vec3}
* @default (0, 0, 0)
*/
this.center = new Vec3(0, 0, 0);
/**
* The center point of the box's bottom. (The origin of the R axis.)
* @type {Vec3}
* @default (-0.5, 0, 0)
*/
this.bottomCenter = new Vec3(-0.5, 0, 0);
/**
* The center point of the box's top. (The end of the R axis.)
* @type {Vec3}
* @default (0.5, 0, 0)
*/
this.topCenter = new Vec3(0.5, 0, 0);
/**
* The box's R axis, its longest axis.
* @type {Vec3}
* @default (1, 0, 0)
*/
this.r = new Vec3(1, 0, 0);
/**
* The box's S axis, its mid-length axis.
* @type {Vec3}
* @default (0, 1, 0)
*/
this.s = new Vec3(0, 1, 0);
/**
* The box's T axis, its shortest axis.
* @type {Vec3}
* @default (0, 0, 1)
*/
this.t = new Vec3(0, 0, 1);
/**
* The box's radius. (The half-length of its diagonal.)
* @type {number}
* @default sqrt(3)
*/
this.radius = Math.sqrt(3);
// Internal use only. Intentionally not documented.
this.tmp1 = new Vec3(0, 0, 0);
this.tmp2 = new Vec3(0, 0, 0);
this.tmp3 = new Vec3(0, 0, 0);
// Internal use only. Intentionally not documented.
this.scratchElevations = new Float64Array(9);
this.scratchPoints = new Float64Array(3 * this.scratchElevations.length);
}
// Internal use only. Intentionally not documented.
BoundingBox.scratchMatrix = Matrix.fromIdentity();
/**
* Returns the eight {@link Vec3} corners of the box.
*
* @returns {Array} the eight box corners in the order bottom-lower-left, bottom-lower-right, bottom-upper-right,
* bottom-upper-left, top-lower-left, top-lower-right, top-upper-right, top-upper-left.
*/
BoundingBox.prototype.getCorners = function () {
var ll = new Vec3(this.s[0], this.s[1], this.s[2]);
var lr = new Vec3(this.t[0], this.t[1], this.t[2]);
var ur = new Vec3(this.s[0], this.s[1], this.s[2]);
var ul = new Vec3(this.s[0], this.s[1], this.s[2]);
ll.add(this.t).multiply(-0.5); // Lower left.
lr.subtract(this.s).multiply(0.5); // Lower right.
ur.add(this.t).multiply(0.5); // Upper right.
ul.subtract(this.t).multiply(0.5); // Upper left.
var corners = [];
for (var i = 0; i < 4; i++) {
corners.push(new Vec3(this.bottomCenter[0], this.bottomCenter[1], this.bottomCenter[2]));
}
for (i = 0; i < 4; i++) {
corners.push(new Vec3(this.topCenter[0], this.topCenter[1], this.topCenter[2]));
}
corners[0].add(ll);
corners[1].add(lr);
corners[2].add(ur);
corners[3].add(ul);
corners[4].add(ll);
corners[5].add(lr);
corners[6].add(ur);
corners[7].add(ul);
return corners;
};
/**
* Sets this bounding box such that it minimally encloses a specified collection of points.
* @param {Float32Array} points The points to contain.
* @returns {BoundingBox} This bounding box set to contain the specified points.
* @throws {ArgumentError} If the specified list of points is null, undefined or empty.
*/
BoundingBox.prototype.setToPoints = function (points) {
if (!points || points.length < 3) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToPoints", "missingArray"));
}
var rMin = +Number.MAX_VALUE,
rMax = -Number.MAX_VALUE,
sMin = +Number.MAX_VALUE,
sMax = -Number.MAX_VALUE,
tMin = +Number.MAX_VALUE,
tMax = -Number.MAX_VALUE,
r = this.r, s = this.s, t = this.t,
p = new Vec3(0, 0, 0),
pdr, pds, pdt, rLen, sLen, tLen, rSum, sSum, tSum,
rx_2, ry_2, rz_2, cx, cy, cz;
Matrix.principalAxesFromPoints(points, r, s, t);
for (var i = 0, len = points.length / 3; i < len; i++) {
p[0] = points[i * 3];
p[1] = points[i * 3 + 1];
p[2] = points[i * 3 + 2];
pdr = p.dot(r);
if (rMin > pdr)
rMin = pdr;
if (rMax < pdr)
rMax = pdr;
pds = p.dot(s);
if (sMin > pds)
sMin = pds;
if (sMax < pds)
sMax = pds;
pdt = p.dot(t);
if (tMin > pdt)
tMin = pdt;
if (tMax < pdt)
tMax = pdt;
}
if (rMax === rMin)
rMax = rMin + 1;
if (sMax === sMin)
sMax = sMin + 1;
if (tMax === tMin)
tMax = tMin + 1;
rLen = rMax - rMin;
sLen = sMax - sMin;
tLen = tMax - tMin;
rSum = rMax + rMin;
sSum = sMax + sMin;
tSum = tMax + tMin;
rx_2 = 0.5 * r[0] * rLen;
ry_2 = 0.5 * r[1] * rLen;
rz_2 = 0.5 * r[2] * rLen;
cx = 0.5 * (r[0] * rSum + s[0] * sSum + t[0] * tSum);
cy = 0.5 * (r[1] * rSum + s[1] * sSum + t[1] * tSum);
cz = 0.5 * (r[2] * rSum + s[2] * sSum + t[2] * tSum);
this.center[0] = cx;
this.center[1] = cy;
this.center[2] = cz;
this.topCenter[0] = cx + rx_2;
this.topCenter[1] = cy + ry_2;
this.topCenter[2] = cz + rz_2;
this.bottomCenter[0] = cx - rx_2;
this.bottomCenter[1] = cy - ry_2;
this.bottomCenter[2] = cz - rz_2;
r.multiply(rLen);
s.multiply(sLen);
t.multiply(tLen);
this.radius = 0.5 * Math.sqrt(rLen * rLen + sLen * sLen + tLen * tLen);
return this;
};
/**
* Sets this bounding box such that it minimally encloses a specified collection of points.
* @param {Vec3} points The points to contain.
* @returns {BoundingBox} This bounding box set to contain the specified points.
* @throws {ArgumentError} If the specified list of points is null, undefined or empty.
*/
BoundingBox.prototype.setToVec3Points = function (points) {
if (!points || points.length === 0) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToVec3Points", "missingArray"));
}
var pointList = new Float32Array(points.length * 3);
for (var i = 0; i < points.length; i++) {
var point = points[i];
for (var j = 0; j < 3; j++) {
pointList[i * 3 + j] = point[j];
}
}
return this.setToPoints(pointList);
};
/**
* Sets this bounding box such that it contains a specified sector on a specified globe with min and max elevation.
* <p>
* To create a bounding box that contains the sector at mean sea level, specify zero for the minimum and maximum
* elevations.
* To create a bounding box that contains the terrain surface in this sector, specify the actual minimum and maximum
* elevation values associated with the sector, multiplied by the model's vertical exaggeration.
* @param {Sector} sector The sector for which to create the bounding box.
* @param {Globe} globe The globe associated with the sector.
* @param {Number} minElevation The minimum elevation within the sector.
* @param {Number} maxElevation The maximum elevation within the sector.
* @returns {BoundingBox} This bounding box set to contain the specified sector.
* @throws {ArgumentError} If either the specified sector or globe is null or undefined.
*/
BoundingBox.prototype.setToSector = function (sector, globe, minElevation, maxElevation) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToSector", "missingSector"));
}
if (!globe) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToSector", "missingGlobe"));
}
// Compute the cartesian points for a 3x3 geographic grid. This grid captures enough detail to bound the
// sector. Use minimum elevation at the corners and max elevation everywhere else.
var elevations = this.scratchElevations,
points = this.scratchPoints;
WWUtil.fillArray(elevations, maxElevation);
elevations[0] = elevations[2] = elevations[6] = elevations[8] = minElevation;
globe.computePointsForGrid(sector, 3, 3, elevations, Vec3.ZERO, points);
// Compute the local coordinate axes. Since we know this box is bounding a geographic sector, we use the
// local coordinate axes at its centroid as the box axes. Using these axes results in a box that has +-10%
// the volume of a box with axes derived from a principal component analysis, but is faster to compute.
var index = 12; // index to the center point's X coordinate
this.tmp1.set(points[index], points[index + 1], points[index + 2]);
WWMath.localCoordinateAxesAtPoint(this.tmp1, globe, this.r, this.s, this.t);
// Find the extremes along each axis.
var rExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
sExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
tExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
for (var i = 0, len = points.length; i < len; i += 3) {
this.tmp1.set(points[i], points[i + 1], points[i + 2]);
this.adjustExtremes(this.r, rExtremes, this.s, sExtremes, this.t, tExtremes, this.tmp1);
}
// If the sector encompasses more than one hemisphere, the 3x3 grid does not capture enough detail to bound
// the sector. The antipodal points along the parallel through the sector's centroid represent its extremes
// in longitude. Incorporate those antipodal points into the extremes along each axis.
if (sector.deltaLongitude() > 180) {
globe.computePointFromPosition(WWMath.mercatorLat(sector.centroidLatitude()), sector.centroidLongitude() + 90, maxElevation, this.tmp1);
globe.computePointFromPosition(WWMath.mercatorLat(sector.centroidLatitude()), sector.centroidLongitude() - 90, maxElevation, this.tmp2);
this.adjustExtremes(this.r, rExtremes, this.s, sExtremes, this.t, tExtremes, this.tmp1);
this.adjustExtremes(this.r, rExtremes, this.s, sExtremes, this.t, tExtremes, this.tmp2);
}
// Sort the axes from most prominent to least prominent. The frustum intersection methods in WWBoundingBox assume
// that the axes are defined in this way.
if (rExtremes[1] - rExtremes[0] < sExtremes[1] - sExtremes[0]) {
this.swapAxes(this.r, rExtremes, this.s, sExtremes);
}
if (sExtremes[1] - sExtremes[0] < tExtremes[1] - tExtremes[0]) {
this.swapAxes(this.s, sExtremes, this.t, tExtremes);
}
if (rExtremes[1] - rExtremes[0] < sExtremes[1] - sExtremes[0]) {
this.swapAxes(this.r, rExtremes, this.s, sExtremes);
}
// Compute the box properties from its unit axes and the extremes along each axis.
var rLen = rExtremes[1] - rExtremes[0],
sLen = sExtremes[1] - sExtremes[0],
tLen = tExtremes[1] - tExtremes[0],
rSum = rExtremes[1] + rExtremes[0],
sSum = sExtremes[1] + sExtremes[0],
tSum = tExtremes[1] + tExtremes[0],
cx = 0.5 * (this.r[0] * rSum + this.s[0] * sSum + this.t[0] * tSum),
cy = 0.5 * (this.r[1] * rSum + this.s[1] * sSum + this.t[1] * tSum),
cz = 0.5 * (this.r[2] * rSum + this.s[2] * sSum + this.t[2] * tSum),
rx_2 = 0.5 * this.r[0] * rLen,
ry_2 = 0.5 * this.r[1] * rLen,
rz_2 = 0.5 * this.r[2] * rLen;
this.center.set(cx, cy, cz);
this.topCenter.set(cx + rx_2, cy + ry_2, cz + rz_2);
this.bottomCenter.set(cx - rx_2, cy - ry_2, cz - rz_2);
this.r.multiply(rLen);
this.s.multiply(sLen);
this.t.multiply(tLen);
this.radius = 0.5 * Math.sqrt(rLen * rLen + sLen * sLen + tLen * tLen);
return this;
};
/**
* Translates this bounding box by a specified translation vector.
* @param {Vec3} translation The translation vector.
* @returns {BoundingBox} This bounding box translated by the specified translation vector.
* @throws {ArgumentError} If the specified translation vector is null or undefined.
*/
BoundingBox.prototype.translate = function (translation) {
if (!translation) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "translate", "missingVector"));
}
this.bottomCenter.add(translation);
this.topCenter.add(translation);
this.center.add(translation);
return this;
};
/**
* Computes the approximate distance between this bounding box and a specified point.
* <p>
* This calculation treats the bounding box as a sphere with the same radius as the box.
* @param {Vec3} point The point to compute the distance to.
* @returns {Number} The distance from the edge of this bounding box to the specified point.
* @throws {ArgumentError} If the specified point is null or undefined.
*/
BoundingBox.prototype.distanceTo = function (point) {
if (!point) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "distanceTo", "missingPoint"));
}
var d = this.center.distanceTo(point) - this.radius;
return d >= 0 ? d : -d;
};
/**
* Computes the effective radius of this bounding box relative to a specified plane.
* @param {Plane} plane The plane of interest.
* @returns {Number} The effective radius of this bounding box to the specified plane.
* @throws {ArgumentError} If the specified plane is null or undefined.
*/
BoundingBox.prototype.effectiveRadius = function (plane) {
if (!plane) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "effectiveRadius", "missingPlane"));
}
var n = plane.normal;
return 0.5 * (WWMath.fabs(this.r.dot(n)) + WWMath.fabs(this.s.dot(n)) + WWMath.fabs(this.t.dot(n)));
};
/**
* Indicates whether this bounding box intersects a specified frustum.
* @param {Frustum} frustum The frustum of interest.
* @returns {boolean} true if the specified frustum intersects this bounding box, otherwise false.
* @throws {ArgumentError} If the specified frustum is null or undefined.
*/
BoundingBox.prototype.intersectsFrustum = function (frustum) {
if (!frustum) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "intersectsFrustum", "missingFrustum"));
}
this.tmp1.copy(this.bottomCenter);
this.tmp2.copy(this.topCenter);
if (this.intersectionPoint(frustum.near) < 0) {
return false;
}
if (this.intersectionPoint(frustum.far) < 0) {
return false;
}
if (this.intersectionPoint(frustum.left) < 0) {
return false;
}
if (this.intersectionPoint(frustum.right) < 0) {
return false;
}
if (this.intersectionPoint(frustum.top) < 0) {
return false;
}
if (this.intersectionPoint(frustum.bottom) < 0) {
return false;
}
return true;
};
// Internal. Intentionally not documented.
BoundingBox.prototype.intersectionPoint = function (plane) {
var n = plane.normal,
effectiveRadius = 0.5 * (Math.abs(this.s.dot(n)) + Math.abs(this.t.dot(n)));
return this.intersectsAt(plane, effectiveRadius, this.tmp1, this.tmp2);
};
// Internal. Intentionally not documented.
BoundingBox.prototype.intersectsAt = function (plane, effRadius, endPoint1, endPoint2) {
// Test the distance from the first end-point.
var dq1 = plane.dot(endPoint1);
var bq1 = dq1 <= -effRadius;
// Test the distance from the second end-point.
var dq2 = plane.dot(endPoint2);
var bq2 = dq2 <= -effRadius;
if (bq1 && bq2) { // endpoints more distant from plane than effective radius; box is on neg. side of plane
return -1;
}
if (bq1 == bq2) { // endpoints less distant from plane than effective radius; can't draw any conclusions
return 0;
}
// Compute and return the endpoints of the box on the positive side of the plane
this.tmp3.copy(endPoint1);
this.tmp3.subtract(endPoint2);
var t = (effRadius + dq1) / plane.normal.dot(this.tmp3);
this.tmp3.copy(endPoint2);
this.tmp3.subtract(endPoint1);
this.tmp3.multiply(t);
this.tmp3.add(endPoint1);
// Truncate the line to only that in the positive halfspace, e.g., inside the frustum.
if (bq1) {
endPoint1.copy(this.tmp3);
}
else {
endPoint2.copy(this.tmp3);
}
return t;
};
// Internal. Intentionally not documented.
BoundingBox.prototype.adjustExtremes = function (r, rExtremes, s, sExtremes, t, tExtremes, p) {
var pdr = p.dot(r);
if (rExtremes[0] > pdr) {
rExtremes[0] = pdr;
}
if (rExtremes[1] < pdr) {
rExtremes[1] = pdr;
}
var pds = p.dot(s);
if (sExtremes[0] > pds) {
sExtremes[0] = pds;
}
if (sExtremes[1] < pds) {
sExtremes[1] = pds;
}
var pdt = p.dot(t);
if (tExtremes[0] > pdt) {
tExtremes[0] = pdt;
}
if (tExtremes[1] < pdt) {
tExtremes[1] = pdt;
}
};
// Internal. Intentionally not documented.
BoundingBox.prototype.swapAxes = function (a, aExtremes, b, bExtremes) {
a.swap(b);
var tmp = aExtremes[0];
aExtremes[0] = bExtremes[0];
bExtremes[0] = tmp;
tmp = aExtremes[1];
aExtremes[1] = bExtremes[1];
bExtremes[1] = tmp;
};
/**
* Renders this bounding box in a semi-transparent color with a highlighted outline. This function is intended
* for diagnostic use only.
* @param dc {DrawContext} dc The current draw context.
*/
BoundingBox.prototype.render = function (dc) {
var gl = dc.currentGlContext,
matrix = BoundingBox.scratchMatrix,
program = dc.findAndBindProgram(BasicProgram);
try {
// Setup to transform unit cube coordinates to this bounding box's local coordinates, as viewed by the
// current navigator state.
matrix.copy(dc.modelviewProjection);
matrix.multiply(
this.r[0], this.s[0], this.t[0], this.center[0],
this.r[1], this.s[1], this.t[1], this.center[1],
this.r[2], this.s[2], this.t[2], this.center[2],
0, 0, 0, 1);
matrix.multiplyByTranslation(-0.5, -0.5, -0.5);
program.loadModelviewProjection(gl, matrix);
// Setup to draw the geometry when the eye point is inside or outside the box.
gl.disable(gl.CULL_FACE);
// Bind the shared unit cube vertex buffer and element buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitCubeBuffer());
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, dc.unitCubeElements());
gl.enableVertexAttribArray(program.vertexPointLocation);
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
// Draw bounding box fragments that are below the terrain.
program.loadColorComponents(gl, 0, 1, 0, 0.6);
gl.drawElements(gl.LINES, 24, gl.UNSIGNED_SHORT, 72);
program.loadColorComponents(gl, 1, 1, 1, 0.3);
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
} finally {
// Restore WorldWind's default WebGL state.
gl.enable(gl.CULL_FACE);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
};
export default BoundingBox;

View File

@ -0,0 +1,315 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Frustum
*/
import ArgumentError from '../error/ArgumentError';
import Plane from '../geom/Plane';
import Logger from '../util/Logger';
/**
* Constructs a frustum.
* @alias Frustum
* @constructor
* @classdesc Represents a six-sided view frustum in Cartesian coordinates.
* @param {Plane} left The frustum's left plane.
* @param {Plane} right The frustum's right plane.
* @param {Plane} bottom The frustum's bottom plane.
* @param {Plane} top The frustum's top plane.
* @param {Plane} near The frustum's near plane.
* @param {Plane} far The frustum's far plane.
* @throws {ArgumentError} If any specified plane is null or undefined.
*/
function Frustum(left, right, bottom, top, near, far) {
if (!left || !right || !bottom || !top || !near || !far) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "constructor", "missingPlane"));
}
// Internal. Intentionally not documented. See property accessors below for public interface.
this._left = left;
this._right = right;
this._bottom = bottom;
this._top = top;
this._near = near;
this._far = far;
// Internal. Intentionally not documented.
this._planes = [this._left, this._right, this._top, this._bottom, this._near, this._far];
}
// These accessors are defined in order to prevent changes that would make the properties inconsistent with the
// planes array.
Object.defineProperties(Frustum.prototype, {
/**
* This frustum's left plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
left: {
get: function () {
return this._left;
}
},
/**
* This frustum's right plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
right: {
get: function () {
return this._right;
}
},
/**
* This frustum's bottom plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
bottom: {
get: function () {
return this._bottom;
}
},
/**
* This frustum's top plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
top: {
get: function () {
return this._top;
}
},
/**
* This frustum's near plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
near: {
get: function () {
return this._near;
}
},
/**
* This frustum's far plane.
* @memberof Frustum.prototype
* @type {Plane}
* @readonly
*/
far: {
get: function () {
return this._far;
}
}
});
/**
* Transforms this frustum by a specified matrix.
* @param {Matrix} matrix The matrix to apply to this frustum.
* @returns {Frustum} This frustum set to its original value multiplied by the specified matrix.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
Frustum.prototype.transformByMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "transformByMatrix", "missingMatrix"));
}
this._left.transformByMatrix(matrix);
this._right.transformByMatrix(matrix);
this._bottom.transformByMatrix(matrix);
this._top.transformByMatrix(matrix);
this._near.transformByMatrix(matrix);
this._far.transformByMatrix(matrix);
return this;
};
/**
* Normalizes the plane vectors of the planes composing this frustum.
* @returns {Frustum} This frustum with its planes normalized.
*/
Frustum.prototype.normalize = function () {
this._left.normalize();
this._right.normalize();
this._bottom.normalize();
this._top.normalize();
this._near.normalize();
this._far.normalize();
return this;
};
/**
* Returns a new frustum with each of its planes 1 meter from the center.
* @returns {Frustum} The new frustum.
*/
Frustum.unitFrustum = function () {
return new Frustum(
new Plane(1, 0, 0, 1), // left
new Plane(-1, 0, 0, 1), // right
new Plane(0, 1, 1, 1), // bottom
new Plane(0, -1, 0, 1), // top
new Plane(0, 0, -1, 1), // near
new Plane(0, 0, 1, 1) // far
);
};
/**
* Extracts a frustum from a projection matrix.
* <p>
* This method assumes that the specified matrix represents a projection matrix. If it does not represent a projection matrix
* the results are undefined.
* <p>
* A projection matrix's view frustum is a Cartesian volume that contains everything visible in a scene displayed
* using that projection matrix.
*
* @param {Matrix} matrix The projection matrix to extract the frustum from.
* @return {Frustum} A new frustum containing the projection matrix's view frustum, in eye coordinates.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
Frustum.fromProjectionMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "fromProjectionMatrix", "missingMatrix"));
}
var x, y, z, w, d, left, right, top, bottom, near, far;
// Left Plane = row 4 + row 1:
x = matrix[12] + matrix[0];
y = matrix[13] + matrix[1];
z = matrix[14] + matrix[2];
w = matrix[15] + matrix[3];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
left = new Plane(x / d, y / d, z / d, w / d);
// Right Plane = row 4 - row 1:
x = matrix[12] - matrix[0];
y = matrix[13] - matrix[1];
z = matrix[14] - matrix[2];
w = matrix[15] - matrix[3];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
right = new Plane(x / d, y / d, z / d, w / d);
// Bottom Plane = row 4 + row 2:
x = matrix[12] + matrix[4];
y = matrix[13] + matrix[5];
z = matrix[14] + matrix[6];
w = matrix[15] + matrix[7];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
bottom = new Plane(x / d, y / d, z / d, w / d);
// Top Plane = row 4 - row 2:
x = matrix[12] - matrix[4];
y = matrix[13] - matrix[5];
z = matrix[14] - matrix[6];
w = matrix[15] - matrix[7];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
top = new Plane(x / d, y / d, z / d, w / d);
// Near Plane = row 4 + row 3:
x = matrix[12] + matrix[8];
y = matrix[13] + matrix[9];
z = matrix[14] + matrix[10];
w = matrix[15] + matrix[11];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
near = new Plane(x / d, y / d, z / d, w / d);
// Far Plane = row 4 - row 3:
x = matrix[12] - matrix[8];
y = matrix[13] - matrix[9];
z = matrix[14] - matrix[10];
w = matrix[15] - matrix[11];
d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
far = new Plane(x / d, y / d, z / d, w / d);
return new Frustum(left, right, bottom, top, near, far);
};
Frustum.prototype.containsPoint = function (point) {
if (!point) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "containsPoint", "missingPoint"));
}
// See if the point is entirely within the frustum. The dot product of the point with each plane's vector
// provides a distance to each plane. If this distance is less than 0, the point is clipped by that plane and
// neither intersects nor is contained by the space enclosed by this Frustum.
if (this._far.dot(point) <= 0)
return false;
if (this._left.dot(point) <= 0)
return false;
if (this._right.dot(point) <= 0)
return false;
if (this._top.dot(point) <= 0)
return false;
if (this._bottom.dot(point) <= 0)
return false;
if (this._near.dot(point) <= 0)
return false;
return true;
};
/**
* Determines whether a line segment intersects this frustum.
*
* @param {Vec3} pointA One end of the segment.
* @param {Vec3} pointB The other end of the segment.
*
* @return {boolean} <code>true</code> if the segment intersects or is contained in this frustum,
* otherwise <code>false</code>.
*
* @throws {ArgumentError} If either point is null or undefined.
*/
Frustum.prototype.intersectsSegment = function (pointA, pointB) {
if (!pointA || !pointB) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "containsPoint", "missingPoint"));
}
// First do a trivial accept test.
if (this.containsPoint(pointA) || this.containsPoint(pointB))
return true;
if (pointA.equals(pointB))
return false;
for (var i = 0, len = this._planes.length; i < len; i++) {
// See if both points are behind the plane and therefore not in the frustum.
if (this._planes[i].onSameSide(pointA, pointB) < 0)
return false;
// See if the segment intersects the plane.
if (this._planes[i].clip(pointA, pointB) != null)
return true;
}
return false; // segment does not intersect frustum
};
export default Frustum;

View File

@ -0,0 +1,140 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Line
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Vec3 from '../geom/Vec3';
/**
* Constructs a line from a specified origin and direction.
* @alias Line
* @constructor
* @classdesc Represents a line in Cartesian coordinates.
* @param {Vec3} origin The line's origin.
* @param {Vec3} direction The line's direction.
* @throws {ArgumentError} If either the origin or the direction are null or undefined.
*/
function Line(origin, direction) {
if (!origin) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "constructor",
"Origin is null or undefined."));
}
if (!direction) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "constructor",
"Direction is null or undefined."));
}
/**
* This line's origin.
* @type {Vec3}
*/
this.origin = origin;
/**
* This line's direction.
* @type {Vec3}
*/
this.direction = direction;
}
/**
* Creates a line given two specified endpoints.
* @param {Vec3} pointA The first endpoint.
* @param {Vec3} pointB The second endpoint.
* @return {Line} The new line.
* @throws {ArgumentError} If either endpoint is null or undefined.
*/
Line.fromSegment = function (pointA, pointB) {
if (!pointA || !pointB) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "fromSegment", "missingPoint"));
}
var origin = new Vec3(pointA[0], pointA[1], pointA[2]),
direction = new Vec3(pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2]);
return new Line(origin, direction);
};
/**
* Computes a Cartesian point a specified distance along this line.
* @param {Number} distance The distance from this line's origin at which to compute the point.
* @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed point.
* @return {Vec3} The specified result argument containing the computed point.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Line.prototype.pointAt = function (distance, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "pointAt", "missingResult."));
}
result[0] = this.origin[0] + this.direction[0] * distance;
result[1] = this.origin[1] + this.direction[1] * distance;
result[2] = this.origin[2] + this.direction[2] * distance;
return result;
};
/**
* Indicates whether the components of this line are equal to those of a specified line.
* @param {Line} otherLine The line to test equality with. May be null or undefined, in which case this
* function returns false.
* @returns {boolean} true if all components of this line are equal to the corresponding
* components of the specified line, otherwise false.
*/
Line.prototype.equals = function (otherLine) {
if (otherLine) {
return this.origin.equals(otherLine.origin) && this.direction.equals(otherLine.direction);
}
return false;
};
/**
* Creates a new line that is a copy of this line.
* @returns {Line} The new line.
*/
Line.prototype.clone = function () {
var clone = new Line(new Vec3(0, 0, 0), new Vec3(0, 0, 0));
clone.copy(this);
return clone;
};
/**
* Copies the components of a specified line to this line.
* @param {Line} copyLine The line to copy.
* @returns {Line} A copy of this line equal to otherLine.
* @throws {ArgumentError} If the specified line is null or undefined.
*/
Line.prototype.copy = function (copyLine) {
if (!copyLine) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "copy", "missingLine"));
}
this.origin.copy(copyLine.origin);
this.direction.copy(copyLine.direction);
return this;
};
export default Line;

View File

@ -0,0 +1,961 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Location
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Plane from '../geom/Plane';
import Vec3 from '../geom/Vec3';
import WWMath from '../util/WWMath';
/**
* Constructs a location from a specified latitude and longitude in degrees.
* @alias Location
* @constructor
* @classdesc Represents a latitude, longitude pair in degrees.
* @param {Number} latitude The latitude in degrees.
* @param {Number} longitude The longitude in degrees.
*/
function Location(latitude, longitude) {
/**
* The latitude in degrees.
* @type {Number}
*/
this.latitude = latitude;
/**
* The longitude in degrees.
* @type {Number}
*/
this.longitude = longitude;
}
/**
* A location with latitude and longitude both 0.
* @constant
* @type {Location}
*/
Location.ZERO = new Location(0, 0);
/**
* Creates a location from angles specified in radians.
* @param {Number} latitudeRadians The latitude in radians.
* @param {Number} longitudeRadians The longitude in radians.
* @returns {Location} The new location with latitude and longitude in degrees.
*/
Location.fromRadians = function (latitudeRadians, longitudeRadians) {
return new Location(latitudeRadians * Angle.RADIANS_TO_DEGREES, longitudeRadians * Angle.RADIANS_TO_DEGREES);
};
/**
* Copies this location to the latitude and longitude of a specified location.
* @param {Location} location The location to copy.
* @returns {Location} This location, set to the values of the specified location.
* @throws {ArgumentError} If the specified location is null or undefined.
*/
Location.prototype.copy = function (location) {
if (!location) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "copy", "missingLocation"));
}
this.latitude = location.latitude;
this.longitude = location.longitude;
return this;
};
/**
* Sets this location to the latitude and longitude.
* @param {Number} latitude The latitude to set.
* @param {Number} longitude The longitude to set.
* @returns {Location} This location, set to the values of the specified latitude and longitude.
*/
Location.prototype.set = function (latitude, longitude) {
this.latitude = latitude;
this.longitude = longitude;
return this;
};
/**
* Indicates whether this location is equal to a specified location.
* @param {Location} location The location to compare this one to.
* @returns {Boolean} <code>true</code> if this location is equal to the specified location, otherwise
* <code>false</code>.
*/
Location.prototype.equals = function (location) {
return location
&& location.latitude === this.latitude && location.longitude === this.longitude;
};
/**
* Compute a location along a path at a specified distance between two specified locations.
* @param {String} pathType The type of path to assume. Recognized values are
* [WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE},
* [WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE} and
* [WorldWind.LINEAR]{@link WorldWind#LINEAR}.
* If the path type is not recognized then WorldWind.LINEAR is used.
* @param {Number} amount The fraction of the path between the two locations at which to compute the new
* location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If either specified location or the result argument is null or undefined.
*/
Location.interpolateAlongPath = function (pathType, amount, location1, location2, result) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateAlongPath", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateAlongPath", "missingResult"));
}
if (pathType === WorldWind.GREAT_CIRCLE) {
return this.interpolateGreatCircle(amount, location1, location2, result);
} else if (pathType && pathType === WorldWind.RHUMB_LINE) {
return this.interpolateRhumb(amount, location1, location2, result);
} else {
return this.interpolateLinear(amount, location1, location2, result);
}
};
/**
* Compute a location along a great circle path at a specified distance between two specified locations.
* @param {Number} amount The fraction of the path between the two locations at which to compute the new
* location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* This function uses a spherical model, not elliptical.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If either specified location or the result argument is null or undefined.
*/
Location.interpolateGreatCircle = function (amount, location1, location2, result) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateGreatCircle", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateGreatCircle", "missingResult"));
}
if (location1.equals(location2)) {
result.latitude = location1.latitude;
result.longitude = location1.longitude;
return result;
}
var t = WWMath.clamp(amount, 0, 1),
azimuthDegrees = this.greatCircleAzimuth(location1, location2),
distanceRadians = this.greatCircleDistance(location1, location2);
return this.greatCircleLocation(location1, azimuthDegrees, t * distanceRadians, result);
};
/**
* Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
* This angle can be used as the starting azimuth for a great circle arc that begins at the first location, and
* passes through the second location.
* This function uses a spherical model, not elliptical.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed azimuth, in degrees.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.greatCircleAzimuth = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleAzimuth", "missingLocation"));
}
var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
x,
y,
azimuthRadians;
if (lat1 == lat2 && lon1 == lon2) {
return 0;
}
if (lon1 == lon2) {
return lat1 > lat2 ? 180 : 0;
}
// Taken from "Map Projections - A Working Manual", page 30, equation 5-4b.
// The atan2() function is used in place of the traditional atan(y/x) to simplify the case when x == 0.
y = Math.cos(lat2) * Math.sin(lon2 - lon1);
x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
azimuthRadians = Math.atan2(y, x);
return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
};
/**
* Computes the great circle angular distance between two locations. The return value gives the distance as the
* angle between the two positions. In radians, this angle is the arc length of the segment between the two
* positions. To compute a distance in meters from this value, multiply the return value by the radius of the
* globe.
* This function uses a spherical model, not elliptical.
*
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed distance, in radians.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.greatCircleDistance = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleDistance", "missingLocation"));
}
var lat1Radians = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2Radians = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1Radians = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2Radians = location2.longitude * Angle.DEGREES_TO_RADIANS,
a,
b,
c,
distanceRadians;
if (lat1Radians == lat2Radians && lon1Radians == lon2Radians) {
return 0;
}
// "Haversine formula," taken from https://en.wikipedia.org/wiki/Great-circle_distance#Formul.C3.A6
a = Math.sin((lat2Radians - lat1Radians) / 2.0);
b = Math.sin((lon2Radians - lon1Radians) / 2.0);
c = a * a + Math.cos(lat1Radians) * Math.cos(lat2Radians) * b * b;
distanceRadians = 2.0 * Math.asin(Math.sqrt(c));
return isNaN(distanceRadians) ? 0 : distanceRadians;
};
/**
* Computes the location on a great circle path corresponding to a given starting location, azimuth, and
* arc distance.
* This function uses a spherical model, not elliptical.
*
* @param {Location} location The starting location.
* @param {Number} greatCircleAzimuthDegrees The azimuth in degrees.
* @param {Number} pathLengthRadians The radian distance along the path at which to compute the end location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If the specified location or the result argument is null or undefined.
*/
Location.greatCircleLocation = function (location, greatCircleAzimuthDegrees, pathLengthRadians, result) {
if (!location) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleLocation", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleLocation", "missingResult"));
}
if (pathLengthRadians == 0) {
result.latitude = location.latitude;
result.longitude = location.longitude;
return result;
}
var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
azimuthRadians = greatCircleAzimuthDegrees * Angle.DEGREES_TO_RADIANS,
endLatRadians,
endLonRadians;
// Taken from "Map Projections - A Working Manual", page 31, equation 5-5 and 5-6.
endLatRadians = Math.asin(Math.sin(latRadians) * Math.cos(pathLengthRadians) +
Math.cos(latRadians) * Math.sin(pathLengthRadians) * Math.cos(azimuthRadians));
endLonRadians = lonRadians + Math.atan2(
Math.sin(pathLengthRadians) * Math.sin(azimuthRadians),
Math.cos(latRadians) * Math.cos(pathLengthRadians) -
Math.sin(latRadians) * Math.sin(pathLengthRadians) * Math.cos(azimuthRadians));
if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
result.latitude = location.latitude;
result.longitude = location.longitude;
} else {
result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
}
return result;
};
/**
* Compute a location along a rhumb path at a specified distance between two specified locations.
* This function uses a spherical model, not elliptical.
* @param {Number} amount The fraction of the path between the two locations at which to compute the new
* location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If either specified location or the result argument is null or undefined.
*/
Location.interpolateRhumb = function (amount, location1, location2, result) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateRhumb", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateRhumb", "missingResult"));
}
if (location1.equals(location2)) {
result.latitude = location1.latitude;
result.longitude = location1.longitude;
return result;
}
var t = WWMath.clamp(amount, 0, 1),
azimuthDegrees = this.rhumbAzimuth(location1, location2),
distanceRadians = this.rhumbDistance(location1, location2);
return this.rhumbLocation(location1, azimuthDegrees, t * distanceRadians, result);
};
/**
* Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
* This angle can be used as the azimuth for a rhumb arc that begins at the first location, and
* passes through the second location.
* This function uses a spherical model, not elliptical.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed azimuth, in degrees.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.rhumbAzimuth = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbAzimuth", "missingLocation"));
}
var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
dLon,
dPhi,
azimuthRadians;
if (lat1 == lat2 && lon1 == lon2) {
return 0;
}
dLon = lon2 - lon1;
dPhi = Math.log(Math.tan(lat2 / 2.0 + Math.PI / 4) / Math.tan(lat1 / 2.0 + Math.PI / 4));
// If lonChange over 180 take shorter rhumb across 180 meridian.
if (WWMath.fabs(dLon) > Math.PI) {
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : 2 * Math.PI + dLon;
}
azimuthRadians = Math.atan2(dLon, dPhi);
return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
};
/**
* Computes the rhumb angular distance between two locations. The return value gives the distance as the
* angle between the two positions in radians. This angle is the arc length of the segment between the two
* positions. To compute a distance in meters from this value, multiply the return value by the radius of the
* globe.
* This function uses a spherical model, not elliptical.
*
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed distance, in radians.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.rhumbDistance = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbDistance", "missingLocation"));
}
var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
dLat,
dLon,
dPhi,
q,
distanceRadians;
if (lat1 == lat2 && lon1 == lon2) {
return 0;
}
dLat = lat2 - lat1;
dLon = lon2 - lon1;
dPhi = Math.log(Math.tan(lat2 / 2.0 + Math.PI / 4) / Math.tan(lat1 / 2.0 + Math.PI / 4));
q = dLat / dPhi;
if (isNaN(dPhi) || isNaN(q)) {
q = Math.cos(lat1);
}
// If lonChange over 180 take shorter rhumb across 180 meridian.
if (WWMath.fabs(dLon) > Math.PI) {
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : 2 * Math.PI + dLon;
}
distanceRadians = Math.sqrt(dLat * dLat + q * q * dLon * dLon);
return isNaN(distanceRadians) ? 0 : distanceRadians;
};
/**
* Computes the location on a rhumb arc with the given starting location, azimuth, and arc distance.
* This function uses a spherical model, not elliptical.
*
* @param {Location} location The starting location.
* @param {Number} azimuthDegrees The azimuth in degrees.
* @param {Number} pathLengthRadians The radian distance along the path at which to compute the location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If the specified location or the result argument is null or undefined.
*/
Location.rhumbLocation = function (location, azimuthDegrees, pathLengthRadians, result) {
if (!location) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbLocation", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbLocation", "missingResult"));
}
if (pathLengthRadians == 0) {
result.latitude = location.latitude;
result.longitude = location.longitude;
return result;
}
var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
azimuthRadians = azimuthDegrees * Angle.DEGREES_TO_RADIANS,
endLatRadians = latRadians + pathLengthRadians * Math.cos(azimuthRadians),
dPhi = Math.log(Math.tan(endLatRadians / 2 + Math.PI / 4) / Math.tan(latRadians / 2 + Math.PI / 4)),
q = (endLatRadians - latRadians) / dPhi,
dLon,
endLonRadians;
if (isNaN(dPhi) || isNaN(q) || !isFinite(q)) {
q = Math.cos(latRadians);
}
dLon = pathLengthRadians * Math.sin(azimuthRadians) / q;
// Handle latitude passing over either pole.
if (WWMath.fabs(endLatRadians) > Math.PI / 2) {
endLatRadians = endLatRadians > 0 ? Math.PI - endLatRadians : -Math.PI - endLatRadians;
}
endLonRadians = WWMath.fmod(lonRadians + dLon + Math.PI, 2 * Math.PI) - Math.PI;
if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
result.latitude = location.latitude;
result.longitude = location.longitude;
} else {
result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
}
return result;
};
/**
* Compute a location along a linear path at a specified distance between two specified locations.
* @param {Number} amount The fraction of the path between the two locations at which to compute the new
* location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If either specified location or the result argument is null or undefined.
*/
Location.interpolateLinear = function (amount, location1, location2, result) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateLinear", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateLinear", "missingResult"));
}
if (location1.equals(location2)) {
result.latitude = location1.latitude;
result.longitude = location1.longitude;
return result;
}
var t = WWMath.clamp(amount, 0, 1),
azimuthDegrees = this.linearAzimuth(location1, location2),
distanceRadians = this.linearDistance(location1, location2);
return this.linearLocation(location1, azimuthDegrees, t * distanceRadians, result);
};
/**
* Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
* This angle can be used as the azimuth for a linear arc that begins at the first location, and
* passes through the second location.
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed azimuth, in degrees.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.linearAzimuth = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearAzimuth", "missingLocation"));
}
var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
dLon,
dPhi,
azimuthRadians;
if (lat1 == lat2 && lon1 == lon2) {
return 0;
}
dLon = lon2 - lon1;
dPhi = lat2 - lat1;
// If longitude change is over 180 take shorter path across 180 meridian.
if (WWMath.fabs(dLon) > Math.PI) {
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : 2 * Math.PI + dLon;
}
azimuthRadians = Math.atan2(dLon, dPhi);
return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
};
/**
* Computes the linear angular distance between two locations. The return value gives the distance as the
* angle between the two positions in radians. This angle is the arc length of the segment between the two
* positions. To compute a distance in meters from this value, multiply the return value by the radius of the
* globe.
*
* @param {Location} location1 The starting location.
* @param {Location} location2 The ending location.
* @returns {Number} The computed distance, in radians.
* @throws {ArgumentError} If either specified location is null or undefined.
*/
Location.linearDistance = function (location1, location2) {
if (!location1 || !location2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearDistance", "missingLocation"));
}
var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
dLat,
dLon,
distanceRadians;
if (lat1 == lat2 && lon1 == lon2) {
return 0;
}
dLat = lat2 - lat1;
dLon = lon2 - lon1;
// If lonChange over 180 take shorter path across 180 meridian.
if (WWMath.fabs(dLon) > Math.PI) {
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : 2 * Math.PI + dLon;
}
distanceRadians = Math.sqrt(dLat * dLat + dLon * dLon);
return isNaN(distanceRadians) ? 0 : distanceRadians;
};
/**
* Computes the location on a linear path with the given starting location, azimuth, and arc distance.
*
* @param {Location} location The starting location.
* @param {Number} azimuthDegrees The azimuth in degrees.
* @param {Number} pathLengthRadians The radian distance along the path at which to compute the location.
* @param {Location} result A Location in which to return the result.
* @returns {Location} The specified result location.
* @throws {ArgumentError} If the specified location or the result argument is null or undefined.
*/
Location.linearLocation = function (location, azimuthDegrees, pathLengthRadians, result) {
if (!location) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearLocation", "missingLocation"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearLocation", "missingResult"));
}
if (pathLengthRadians == 0) {
result.latitude = location.latitude;
result.longitude = location.longitude;
return result;
}
var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
azimuthRadians = azimuthDegrees * Angle.DEGREES_TO_RADIANS,
endLatRadians = latRadians + pathLengthRadians * Math.cos(azimuthRadians),
endLonRadians;
// Handle latitude passing over either pole.
if (WWMath.fabs(endLatRadians) > Math.PI / 2) {
endLatRadians = endLatRadians > 0 ? Math.PI - endLatRadians : -Math.PI - endLatRadians;
}
endLonRadians =
WWMath.fmod(lonRadians + pathLengthRadians * Math.sin(azimuthRadians) + Math.PI, 2 * Math.PI) - Math.PI;
if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
result.latitude = location.latitude;
result.longitude = location.longitude;
} else {
result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
}
return result;
};
/**
* Determine whether a list of locations crosses the dateline.
* @param {Location[]} locations The locations to test.
* @returns {boolean} True if the dateline is crossed, else false.
* @throws {ArgumentError} If the locations list is null.
*/
Location.locationsCrossDateLine = function (locations) {
if (!locations) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "locationsCrossDateline", "missingLocation"));
}
var pos = null;
for (var idx = 0, len = locations.length; idx < len; idx += 1) {
var posNext = locations[idx];
if (pos != null) {
// A segment cross the line if end pos have different longitude signs
// and are more than 180 degrees longitude apart
if (WWMath.signum(pos.longitude) != WWMath.signum(posNext.longitude)) {
var delta = Math.abs(pos.longitude - posNext.longitude);
if (delta > 180 && delta < 360)
return true;
}
}
pos = posNext;
}
return false;
};
/**
* Returns two locations with the most extreme latitudes on the sequence of great circle arcs defined by each pair
* of locations in the specified iterable.
*
* @param {Location[]} locations The pairs of locations defining a sequence of great circle arcs.
*
* @return {Location[]} Two locations with the most extreme latitudes on the great circle arcs.
*
* @throws IllegalArgumentException if locations is null.
*/
Location.greatCircleArcExtremeLocations = function (locations) {
if (!locations) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeLocations", "missingLocation"));
}
var minLatLocation = null;
var maxLatLocation = null;
var lastLocation = null;
for (var idx = 0, len = locations.length; idx < len; idx += 1) {
var location = locations[idx];
if (lastLocation != null) {
var extremes = Location.greatCircleArcExtremeForTwoLocations(lastLocation, location);
if (extremes == null) {
continue;
}
if (minLatLocation == null || minLatLocation.latitude > extremes[0].latitude) {
minLatLocation = extremes[0];
}
if (maxLatLocation == null || maxLatLocation.latitude < extremes[1].latitude) {
maxLatLocation = extremes[1];
}
}
lastLocation = location;
}
return [minLatLocation, maxLatLocation];
};
/**
* Returns two locations with the most extreme latitudes on the great circle arc defined by, and limited to, the two
* locations.
*
* @param {Location} begin Beginning location on the great circle arc.
* @param {Location} end Ending location on the great circle arc.
*
* @return {Location[]} Two locations with the most extreme latitudes on the great circle arc.
*
* @throws {ArgumentError} If either begin or end are null.
*/
Location.greatCircleArcExtremeForTwoLocations = function (begin, end) {
if (!begin || !end) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeForTwoLocations", "missingLocation"));
}
var idx, len, location; // Iteration variables.
var minLatLocation = null;
var maxLatLocation = null;
var minLat = 90;
var maxLat = -90;
// Compute the min and max latitude and associated locations from the arc endpoints.
var locations = [begin, end];
for (idx = 0, len = locations.length; idx < len; idx += 1) {
location = locations[idx];
if (minLat >= location.latitude) {
minLat = location.latitude;
minLatLocation = location;
}
if (maxLat <= location.latitude) {
maxLat = location.latitude;
maxLatLocation = location;
}
}
// The above could be written for greater clarity, simplicity, and speed:
// minLat = Math.min(begin.latitude, end.latitude);
// maxLat = Math.max(begin.latitude, end.latitude);
// minLatLocation = minLat == begin.latitude ? begin : end;
// maxLatLocation = maxLat == begin.latitude ? begin : end;
// Compute parameters for the great circle arc defined by begin and end. Then compute the locations of extreme
// latitude on entire the great circle which that arc is part of.
var greatArcAzimuth = Location.greatCircleAzimuth(begin, end);
var greatArcDistance = Location.greatCircleDistance(begin, end);
var greatCircleExtremes = Location.greatCircleExtremeLocationsUsingAzimuth(begin, greatArcAzimuth);
// Determine whether either of the extreme locations are inside the arc defined by begin and end. If so,
// adjust the min and max latitude accordingly.
for (idx = 0, len = greatCircleExtremes.length; idx < len; idx += 1) {
location = greatCircleExtremes[idx];
var az = Location.greatCircleAzimuth(begin, location);
var d = Location.greatCircleDistance(begin, location);
// The extreme location must be between the begin and end locations. Therefore its azimuth relative to
// the begin location should have the same signum, and its distance relative to the begin location should
// be between 0 and greatArcDistance, inclusive.
if (WWMath.signum(az) == WWMath.signum(greatArcAzimuth)) {
if (d >= 0 && d <= greatArcDistance) {
if (minLat >= location.latitude) {
minLat = location.latitude;
minLatLocation = location;
}
if (maxLat <= location.latitude) {
maxLat = location.latitude;
maxLatLocation = location;
}
}
}
}
return [minLatLocation, maxLatLocation];
};
/**
* Returns two locations with the most extreme latitudes on the great circle with the given starting location and
* azimuth.
*
* @param {Location} location Location on the great circle.
* @param {number} azimuth Great circle azimuth angle (clockwise from North).
*
* @return {Location[]} Two locations where the great circle has its extreme latitudes.
*
* @throws {ArgumentError} If location is null.
*/
Location.greatCircleExtremeLocationsUsingAzimuth = function (location, azimuth) {
if (!location) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeLocationsUsingAzimuth", "missingLocation"));
}
var lat0 = location.latitude;
var az = azimuth * Angle.DEGREES_TO_RADIANS;
// Derived by solving the function for longitude on a great circle against the desired longitude. We start
// with the equation in "Map Projections - A Working Manual", page 31, equation 5-5:
//
// lat = asin( sin(lat0) * cos(C) + cos(lat0) * sin(C) * cos(Az) )
//
// Where (lat0, lon) are the starting coordinates, c is the angular distance along the great circle from the
// starting coordinate, and Az is the azimuth. All values are in radians. Solving for angular distance gives
// distance to the equator:
//
// tan(C) = -tan(lat0) / cos(Az)
//
// The great circle is by definition centered about the Globe's origin. Therefore intersections with the
// equator will be antipodal (exactly 180 degrees opposite each other), as will be the extreme latitudes.
// By observing the symmetry of a great circle, it is also apparent that the extreme latitudes will be 90
// degrees from either intersection with the equator.
//
// d1 = c + 90
// d2 = c - 90
var tanDistance = -Math.tan(lat0) / Math.cos(az);
var distance = Math.atan(tanDistance);
var extremeDistance1 = distance + Math.PI / 2.0;
var extremeDistance2 = distance - Math.PI / 2.0;
return [
Location.greatCircleLocation(location, azimuth, extremeDistance1, new Location(0, 0)),
Location.greatCircleLocation(location, azimuth, extremeDistance2, new Location(0, 0))
];
};
/**
* Determine where a line between two positions crosses a given meridian. The intersection test is performed by
* intersecting a line in Cartesian space between the two positions with a plane through the meridian. Thus, it is
* most suitable for working with positions that are fairly close together as the calculation does not take into
* account great circle or rhumb paths.
*
* @param {Location} p1 First position.
* @param {Location} p2 Second position.
* @param {number} meridian Longitude line to intersect with.
* @param {Globe} globe Globe used to compute intersection.
*
* @return {number} latitude The intersection latitude along the meridian
*
* TODO: this code allocates 4 new Vec3 and 1 new Position; use scratch variables???
* TODO: Why not? Every location created would then allocated those variables as well, even if they aren't needed :(.
*/
Location.intersectionWithMeridian = function (p1, p2, meridian, globe) {
// TODO: add support for 2D
//if (globe instanceof Globe2D)
//{
// // y = mx + b case after normalizing negative angles.
// double lon1 = p1.getLongitude().degrees < 0 ? p1.getLongitude().degrees + 360 : p1.getLongitude().degrees;
// double lon2 = p2.getLongitude().degrees < 0 ? p2.getLongitude().degrees + 360 : p2.getLongitude().degrees;
// if (lon1 == lon2)
// return null;
//
// double med = meridian.degrees < 0 ? meridian.degrees + 360 : meridian.degrees;
// double slope = (p2.latitude.degrees - p1.latitude.degrees) / (lon2 - lon1);
// double lat = p1.latitude.degrees + slope * (med - lon1);
//
// return LatLon.fromDegrees(lat, meridian.degrees);
//}
var pt1 = globe.computePointFromLocation(p1.latitude, p1.longitude, new Vec3(0, 0, 0));
var pt2 = globe.computePointFromLocation(p2.latitude, p2.longitude, new Vec3(0, 0, 0));
// Compute a plane through the origin, North Pole, and the desired meridian.
var northPole = globe.computePointFromLocation(90, meridian, new Vec3(0, 0, 0));
var pointOnEquator = globe.computePointFromLocation(0, meridian, new Vec3(0, 0, 0));
var plane = Plane.fromPoints(northPole, pointOnEquator, Vec3.ZERO);
var intersectionPoint = new Vec3(0, 0, 0);
if (!plane.intersectsSegmentAt(pt1, pt2, intersectionPoint)) {
return null;
}
// TODO: unable to simply create a new Position(0, 0, 0)
var pos = new WorldWind.Position(0, 0, 0);
globe.computePositionFromPoint(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2], pos);
return pos.latitude;
};
/**
* Determine where a line between two positions crosses a given meridian. The intersection test is performed by
* intersecting a line in Cartesian space. Thus, it is most suitable for working with positions that are fairly
* close together as the calculation does not take into account great circle or rhumb paths.
*
* @param {Location | Position} p1 First position.
* @param {Location | Position} p2 Second position.
* @param {number} meridian Longitude line to intersect with.
*
* @return {number | null} latitude The intersection latitude along the meridian
* or null if the line is collinear with the meridian
*/
Location.meridianIntersection = function (p1, p2, meridian) {
// y = mx + b case after normalizing negative angles.
var lon1 = p1.longitude < 0 ? p1.longitude + 360 : p1.longitude;
var lon2 = p2.longitude < 0 ? p2.longitude + 360 : p2.longitude;
if (lon1 === lon2) {
//infinite solutions, the line is collinear with the anti-meridian
return null;
}
var med = meridian < 0 ? meridian + 360 : meridian;
var slope = (p2.latitude - p1.latitude) / (lon2 - lon1);
var lat = p1.latitude + slope * (med - lon1);
return lat;
};
/**
* A bit mask indicating which if any pole is being referenced.
* This corresponds to Java WW's AVKey.NORTH and AVKey.SOUTH,
* although this encoding can capture both poles simultaneously, which was
* a 'to do' item in the Java implementation.
* @type {{NONE: number, NORTH: number, SOUTH: number}}
*/
Location.poles = {
'NONE': 0,
'NORTH': 1,
'SOUTH': 2
};
export default Location;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,228 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Matrix3
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
/**
* Constructs a 3 x 3 matrix.
* @alias Matrix3
* @constructor
* @classdesc Represents a 3 x 3 double precision matrix stored in a Float64Array in row-major order.
* @param {Number} m11 matrix element at row 1, column 1.
* @param {Number} m12 matrix element at row 1, column 2.
* @param {Number} m13 matrix element at row 1, column 3.
* @param {Number} m21 matrix element at row 2, column 1.
* @param {Number} m22 matrix element at row 2, column 2.
* @param {Number} m23 matrix element at row 2, column 3.
* @param {Number} m31 matrix element at row 3, column 1.
* @param {Number} m32 matrix element at row 3, column 2.
* @param {Number} m33 matrix element at row 3, column 3.
*/
function Matrix3(m11, m12, m13,
m21, m22, m23,
m31, m32, m33) {
this[0] = m11;
this[1] = m12;
this[2] = m13;
this[3] = m21;
this[4] = m22;
this[5] = m23;
this[6] = m31;
this[7] = m32;
this[8] = m33;
}
// Derives from Float64Array.
Matrix3.prototype = new Float64Array(9);
/**
* Creates an identity matrix.
* @returns {Matrix3} A new identity matrix.
*/
Matrix3.fromIdentity = function () {
return new Matrix3(
1, 0, 0,
0, 1, 0,
0, 0, 1
);
};
/**
* Sets this matrix to one that flips and shifts the y-axis.
* <p>
* The resultant matrix maps Y=0 to Y=1 and Y=1 to Y=0. All existing values are overwritten. This matrix is
* usually used to change the coordinate origin from an upper left coordinate origin to a lower left coordinate
* origin. This is typically necessary to align the coordinate system of images (top-left origin) with that of
* OpenGL (bottom-left origin).
* @returns {Matrix3} This matrix set to values described above.
*/
Matrix3.prototype.setToUnitYFlip = function () {
this[0] = 1;
this[1] = 0;
this[2] = 0;
this[3] = 0;
this[4] = -1;
this[5] = 1;
this[6] = 0;
this[7] = 0;
this[8] = 1;
return this;
};
/**
* Multiplies this matrix by a specified matrix.
*
* @param {Matrix3} matrix The matrix to multiply with this matrix.
* @returns {Matrix3} This matrix after multiplying it by the specified matrix.
* @throws {ArgumentError} if the specified matrix is null or undefined.
*/
Matrix3.prototype.multiplyMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix3", "multiplyMatrix", "missingMatrix"));
}
var ma = this,
mb = matrix,
ma0, ma1, ma2;
// Row 1
ma0 = ma[0];
ma1 = ma[1];
ma2 = ma[2];
ma[0] = ma0 * mb[0] + ma1 * mb[3] + ma2 * mb[6];
ma[1] = ma0 * mb[1] + ma1 * mb[4] + ma2 * mb[7];
ma[2] = ma0 * mb[2] + ma1 * mb[5] + ma2 * mb[8];
// Row 2
ma0 = ma[3];
ma1 = ma[4];
ma2 = ma[5];
ma[3] = ma0 * mb[0] + ma1 * mb[3] + ma2 * mb[6];
ma[4] = ma0 * mb[1] + ma1 * mb[4] + ma2 * mb[7];
ma[5] = ma0 * mb[2] + ma1 * mb[5] + ma2 * mb[8];
// Row 3
ma0 = ma[6];
ma1 = ma[7];
ma2 = ma[8];
ma[6] = ma0 * mb[0] + ma1 * mb[3] + ma2 * mb[6];
ma[7] = ma0 * mb[1] + ma1 * mb[4] + ma2 * mb[7];
ma[8] = ma0 * mb[2] + ma1 * mb[5] + ma2 * mb[8];
return this;
};
/**
* Multiplies this matrix by a matrix that transforms normalized coordinates from a source sector to a destination
* sector. Normalized coordinates within a sector range from 0 to 1, with (0, 0) indicating the lower left corner
* and (1, 1) indicating the upper right. The resultant matrix maps a normalized source coordinate (X, Y) to its
* corresponding normalized destination coordinate (X', Y').
* <p/>
* This matrix typically necessary to transform texture coordinates from one geographic region to another. For
* example, the texture coordinates for a terrain tile spanning one region must be transformed to coordinates
* appropriate for an image tile spanning a potentially different region.
*
* @param {Sector} src the source sector
* @param {Sector} dst the destination sector
*
* @returns {Matrix3} this matrix multiplied by the transform matrix implied by values described above
*/
Matrix3.prototype.multiplyByTileTransform = function (src, dst) {
var srcDeltaLat = src.deltaLatitude();
var srcDeltaLon = src.deltaLongitude();
var dstDeltaLat = dst.deltaLatitude();
var dstDeltaLon = dst.deltaLongitude();
var xs = srcDeltaLon / dstDeltaLon;
var ys = srcDeltaLat / dstDeltaLat;
var xt = (src.minLongitude - dst.minLongitude) / dstDeltaLon;
var yt = (src.minLatitude - dst.minLatitude) / dstDeltaLat;
// This is equivalent to the following operation, but is potentially much faster:
/*var m = new Matrix3(
xs, 0, xt,
0, ys, yt,
0, 0, 1);
this.multiplyMatrix(m);*/
// This inline version eliminates unnecessary multiplication by 1 and 0 in the matrix's components, reducing
// the total number of primitive operations from 63 to 18.
var m = this;
// Must be done before modifying m0, m1, etc. below.
m[2] += m[0] * xt + m[1] * yt;
m[5] += m[3] * xt + m[4] * yt;
m[8] += m[6] * xt + m[6] * yt;
m[0] *= xs;
m[1] *= ys;
m[3] *= xs;
m[4] *= ys;
m[6] *= xs;
m[7] *= ys;
return this;
};
/**
* Stores this matrix's components in column-major order in a specified array.
* <p>
* The array must have space for at least 9 elements. This matrix's components are stored in the array
* starting with row 0 column 0 in index 0, row 1 column 0 in index 1, row 2 column 0 in index 2, and so on.
*
* @param {Float32Array | Float64Array | Number[]} result An array of at least 9 elements. Upon return,
* contains this matrix's components in column-major.
* @returns {Float32Array} The specified result array.
* @throws {ArgumentError} If the specified result array in null or undefined.
*/
Matrix3.prototype.columnMajorComponents = function (result) {
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix3", "columnMajorComponents", "missingResult"));
}
// Column 1
result[0] = this[0];
result[1] = this[3];
result[2] = this[6];
// Column 2
result[3] = this[1];
result[4] = this[4];
result[5] = this[7];
// Column 3
result[6] = this[2];
result[7] = this[5];
result[8] = this[8];
return result;
};
export default Matrix3;

View File

@ -0,0 +1,287 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Plane
*/
import ArgumentError from '../error/ArgumentError';
import Line from '../geom/Line';
import Logger from '../util/Logger';
import Vec3 from '../geom/Vec3';
/**
* Constructs a plane.
* This constructor does not normalize the components. It assumes that a unit normal vector is provided.
* @alias Plane
* @constructor
* @classdesc Represents a plane in Cartesian coordinates.
* The plane's X, Y and Z components indicate the plane's normal vector. The distance component
* indicates the plane's distance from the origin relative to its unit normal.
* The components are expected to be normalized.
* @param {Number} x The X coordinate of the plane's unit normal vector.
* @param {Number} y The Y coordinate of the plane's unit normal vector.
* @param {Number} z The Z coordinate of the plane's unit normal vector.
* @param {Number} distance The plane's distance from the origin.
*/
function Plane(x, y, z, distance) {
/**
* The normal vector to the plane.
* @type {Vec3}
*/
this.normal = new Vec3(x, y, z);
/**
* The plane's distance from the origin.
* @type {Number}
*/
this.distance = distance;
}
/**
* Computes a plane that passes through the specified three points.
* The plane's normal is the cross product of the
* two vectors from pb to pa and pc to pa, respectively. The
* returned plane is undefined if any of the specified points are colinear.
*
* @param {Vec3} pa The first point.
* @param {Vec3} pb The second point.
* @param {Vec3} pc The third point.
*
* @return {Plane} A plane passing through the specified points.
*
* @throws {ArgumentError} if pa, pb, or pc is null or undefined.
*/
Plane.fromPoints = function (pa, pb, pc) {
if (!pa || !pb || !pc) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "fromPoints", "missingVector"));
}
var vab = new Vec3(pb[0], pb[1], pb[2]);
vab.subtract(pa);
var vac = new Vec3(pc[0], pc[1], pc[2]);
vac.subtract(pa);
vab.cross(vac);
vab.normalize();
var d = -vab.dot(pa);
return new Plane(vab[0], vab[1], vab[2], d);
};
/**
* Computes the dot product of this plane's normal vector with a specified vector.
* Since the plane was defined with a unit normal vector, this function returns the distance of the vector from
* the plane.
* @param {Vec3} vector The vector to dot with this plane's normal vector.
* @returns {Number} The computed dot product.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Plane.prototype.dot = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "dot", "missingVector"));
}
return this.normal.dot(vector) + this.distance;
};
/**
* Computes the distance between this plane and a point.
* @param {Vec3} point The point whose distance to compute.
* @returns {Number} The computed distance.
* @throws {ArgumentError} If the specified point is null or undefined.
*/
Plane.prototype.distanceToPoint = function (point) {
return this.dot(point);
};
/**
* Transforms this plane by a specified matrix.
* @param {Matrix} matrix The matrix to apply to this plane.
* @returns {Plane} This plane transformed by the specified matrix.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
Plane.prototype.transformByMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "transformByMatrix", "missingMatrix"));
}
var x = matrix[0] * this.normal[0] + matrix[1] * this.normal[1] + matrix[2] * this.normal[2] + matrix[3] * this.distance,
y = matrix[4] * this.normal[0] + matrix[5] * this.normal[1] + matrix[6] * this.normal[2] + matrix[7] * this.distance,
z = matrix[8] * this.normal[0] + matrix[9] * this.normal[1] + matrix[10] * this.normal[2] + matrix[11] * this.distance,
distance = matrix[12] * this.normal[0] + matrix[13] * this.normal[1] + matrix[14] * this.normal[2] + matrix[15] * this.distance;
this.normal[0] = x;
this.normal[1] = y;
this.normal[2] = z;
this.distance = distance;
return this;
};
/**
* Normalizes the components of this plane.
* @returns {Plane} This plane with its components normalized.
*/
Plane.prototype.normalize = function () {
var magnitude = this.normal.magnitude();
if (magnitude === 0)
return this;
this.normal.divide(magnitude);
this.distance /= magnitude;
return this;
};
/**
* Determines whether a specified line segment intersects this plane.
*
* @param {Vec3} endPoint1 The first end point of the line segment.
* @param {Vec3} endPoint2 The second end point of the line segment.
* @returns {Boolean} true if the line segment intersects this plane, otherwise false.
*/
Plane.prototype.intersectsSegment = function (endPoint1, endPoint2) {
var distance1 = this.dot(endPoint1),
distance2 = this.dot(endPoint2);
return distance1 * distance2 <= 0;
};
/**
* Computes the intersection point of this plane with a specified line segment.
*
* @param {Vec3} endPoint1 The first end point of the line segment.
* @param {Vec3} endPoint2 The second end point of the line segment.
* @param {Vec3} result A variable in which to return the intersection point of the line segment with this plane.
* @returns {Boolean} true If the line segment intersects this plane, otherwise false.
*/
Plane.prototype.intersectsSegmentAt = function (endPoint1, endPoint2, result) {
// Compute the distance from the end-points.
var distance1 = this.dot(endPoint1),
distance2 = this.dot(endPoint2);
// If both points points lie on the plane, ...
if (distance1 === 0 && distance2 === 0) {
// Choose an arbitrary endpoint as the intersection.
result[0] = endPoint1[0];
result[1] = endPoint1[1];
result[2] = endPoint1[2];
return true;
}
else if (distance1 === distance2) {
// The intersection is undefined.
return false;
}
var weight1 = -distance1 / (distance2 - distance1),
weight2 = 1 - weight1;
result[0] = weight1 * endPoint1[0] + weight2 * endPoint2[0];
result[1] = weight1 * endPoint1[1] + weight2 * endPoint2[1];
result[2] = weight1 * endPoint1[2] + weight2 * endPoint2[2];
return distance1 * distance2 <= 0;
};
/**
* Determines whether two points are on the same side of this plane.
*
* @param {Vec3} pointA the first point.
* @param {Vec3} pointB the second point.
*
* @return {Number} -1 If both points are on the negative side of this plane, +1 if both points are on the
* positive side of this plane, 0 if the points are on opposite sides of this plane.
*
* @throws {ArgumentError} If either point is null or undefined.
*/
Plane.prototype.onSameSide = function (pointA, pointB) {
if (!pointA || !pointB) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "onSameSide", "missingPoint"));
}
var da = this.distanceToPoint(pointA),
db = this.distanceToPoint(pointB);
if (da < 0 && db < 0)
return -1;
if (da > 0 && db > 0)
return 1;
return 0;
};
/**
* Clips a line segment to this plane.
* @param {Vec3} pointA The first line segment endpoint.
* @param {Vec3} pointB The second line segment endpoint.
*
* @returns {Vec3[]} An array of two points both on the positive side of the plane. If the direction of the line formed by the
* two points is positive with respect to this plane's normal vector, the first point in the array will be
* the intersection point on the plane, and the second point will be the original segment end point. If the
* direction of the line is negative with respect to this plane's normal vector, the first point in the
* array will be the original segment's begin point, and the second point will be the intersection point on
* the plane. If the segment does not intersect the plane, null is returned. If the segment is coincident
* with the plane, the input points are returned, in their input order.
*
* @throws {ArgumentError} If either point is null or undefined.
*/
Plane.prototype.clip = function (pointA, pointB) {
if (!pointA || !pointB) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "clip", "missingPoint"));
}
if (pointA.equals(pointB)) {
return null;
}
// Get the projection of the segment onto the plane.
var line = Line.fromSegment(pointA, pointB),
lDotV = this.normal.dot(line.direction),
lDotS, t, p;
// Are the line and plane parallel?
if (lDotV === 0) { // line and plane are parallel and may be coincident.
lDotS = this.dot(line.origin);
if (lDotS === 0) {
return [pointA, pointB]; // line is coincident with the plane
} else {
return null; // line is not coincident with the plane.
}
}
// Not parallel so the line intersects. But does the segment intersect?
t = -this.dot(line.origin) / lDotV; // lDotS / lDotV
if (t < 0 || t > 1) { // segment does not intersect
return null;
}
p = line.pointAt(t, new Vec3(0, 0, 0));
if (lDotV > 0) {
return [p, pointB];
} else {
return [pointA, p];
}
};
export default Plane;

View File

@ -0,0 +1,207 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Position
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import Location from '../geom/Location';
import Logger from '../util/Logger';
import WWMath from '../util/WWMath';
/**
* Constructs a position from a specified latitude and longitude in degrees and altitude in meters.
* @alias Position
* @constructor
* @classdesc Represents a latitude, longitude, altitude triple, with latitude and longitude in degrees and
* altitude in meters.
* @param {Number} latitude The latitude in degrees.
* @param {Number} longitude The longitude in degrees.
* @param {Number} altitude The altitude in meters.
*/
function Position(latitude, longitude, altitude) {
/**
* The latitude in degrees.
* @type {Number}
*/
this.latitude = latitude;
/**
* The longitude in degrees.
* @type {Number}
*/
this.longitude = longitude;
/**
* The altitude in meters.
* @type {Number}
*/
this.altitude = altitude;
}
/**
* A Position with latitude, longitude and altitude all 0.
* @constant
* @type {Position}
*/
Position.ZERO = new Position(0, 0, 0);
/**
* Creates a position from angles specified in radians.
* @param {Number} latitudeRadians The latitude in radians.
* @param {Number} longitudeRadians The longitude in radians.
* @param {Number} altitude The altitude in meters.
* @returns {Position} The new position with latitude and longitude in degrees.
*/
Position.fromRadians = function (latitudeRadians, longitudeRadians, altitude) {
return new Position(
latitudeRadians * Angle.RADIANS_TO_DEGREES,
longitudeRadians * Angle.RADIANS_TO_DEGREES,
altitude);
};
/**
* Sets this position to the latitude, longitude and altitude of a specified position.
* @param {Position} position The position to copy.
* @returns {Position} This position, set to the values of the specified position.
* @throws {ArgumentError} If the specified position is null or undefined.
*/
Position.prototype.copy = function (position) {
if (!position) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "copy", "missingPosition"));
}
this.latitude = position.latitude;
this.longitude = position.longitude;
this.altitude = position.altitude;
return this;
};
/**
* Indicates whether this position has the same latitude, longitude and altitude as a specified position.
* @param {Position} position The position to compare with this one.
* @returns {Boolean} true if this position is equal to the specified one, otherwise false.
*/
Position.prototype.equals = function (position) {
return position
&& position.latitude === this.latitude
&& position.longitude === this.longitude
&& position.altitude === this.altitude;
};
/**
* Computes a position along a great circle path at a specified distance between two specified positions.
* @param {Number} amount The fraction of the path between the two positions at which to compute the new
* position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Position} position1 The starting position.
* @param {Position} position2 The ending position.
* @param {Position} result A Position in which to return the result.
* @returns {Position} The specified result position.
* @throws {ArgumentError} If either specified position or the result argument is null or undefined.
*/
Position.interpolateGreatCircle = function (amount, position1, position2, result) {
if (!position1 || !position2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateGreatCircle", "missingPosition"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateGreatCircle", "missingResult"));
}
var t = WWMath.clamp(amount, 0, 1);
result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);
//noinspection JSCheckFunctionSignatures
Location.interpolateGreatCircle(t, position1, position2, result);
return result;
};
/**
* Computes a position along a rhumb path at a specified distance between two specified positions.
* @param {Number} amount The fraction of the path between the two positions at which to compute the new
* position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Position} position1 The starting position.
* @param {Position} position2 The ending position.
* @param {Position} result A Position in which to return the result.
* @returns {Position} The specified result position.
* @throws {ArgumentError} If either specified position or the result argument is null or undefined.
*/
Position.interpolateRhumb = function (amount, position1, position2, result) {
if (!position1 || !position2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateRhumb", "missingPosition"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateRhumb", "missingResult"));
}
var t = WWMath.clamp(amount, 0, 1);
result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);
//noinspection JSCheckFunctionSignatures
Location.interpolateRhumb(t, position1, position2, result);
return result;
};
/**
* Computes a position along a linear path at a specified distance between two specified positions.
* @param {Number} amount The fraction of the path between the two positions at which to compute the new
* position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
* @param {Position} position1 The starting position.
* @param {Position} position2 The ending position.
* @param {Position} result A Position in which to return the result.
* @returns {Position} The specified result position.
* @throws {ArgumentError} If either specified position or the result argument is null or undefined.
*/
Position.interpolateLinear = function (amount, position1, position2, result) {
if (!position1 || !position2) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateLinear", "missingPosition"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateLinear", "missingResult"));
}
var t = WWMath.clamp(amount, 0, 1);
result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);
//noinspection JSCheckFunctionSignatures
Location.interpolateLinear(t, position1, position2, result);
return result;
};
/**
* Returns a string representation of this position.
* @returns {String}
*/
Position.prototype.toString = function () {
return "(" + this.latitude.toString() + "\u00b0, " + this.longitude.toString() + "\u00b0, "
+ this.altitude.toString() + ")";
};
export default Position;

View File

@ -0,0 +1,161 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Rectangle
*/
/**
* Constructs a rectangle with a specified origin and size.
* @alias Rectangle
* @constructor
* @classdesc Represents a rectangle in 2D Cartesian coordinates.
* @param {Number} x The X coordinate of the rectangle's origin.
* @param {Number} y The Y coordinate of the rectangle's origin.
* @param {Number} width The rectangle's width.
* @param {Number} height The rectangle's height.
*/
function Rectangle(x, y, width, height) {
/**
* The X coordinate of this rectangle's origin.
* @type {Number}
*/
this.x = x;
/**
* The Y coordinate of this rectangle's origin.
* @type {Number}
*/
this.y = y;
/**
* This rectangle's width.
* @type {Number}
*/
this.width = width;
/**
* This rectangle's height.
* @type {Number}
*/
this.height = height;
}
/**
* Sets all this rectangle's properties.
* @param {Number} x The X coordinate of the rectangle's origin.
* @param {Number} y The Y coordinate of the rectangle's origin.
* @param {Number} width The rectangle's width.
* @param {Number} height The rectangle's height.
*/
Rectangle.prototype.set = function (x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
/**
* Returns the minimum X value of this rectangle.
* @returns {Number} The rectangle's minimum X value.
*/
Rectangle.prototype.getMinX = function () {
return this.x;
};
/**
* Returns the minimum Y value of this rectangle.
* @returns {Number} The rectangle's minimum Y value.
*/
Rectangle.prototype.getMinY = function () {
return this.y;
};
/**
* Returns the maximum X value of this rectangle.
* @returns {Number} The rectangle's maximum X value.
*/
Rectangle.prototype.getMaxX = function () {
return this.x + this.width;
};
/**
* Returns the maximum Y value of this rectangle.
* @returns {Number} The rectangle's maximum Y value.
*/
Rectangle.prototype.getMaxY = function () {
return this.y + this.height;
};
/**
* Indicates whether this rectangle contains a specified point.
* @param {Vec2} point The point to test.
* @returns {Boolean} true if this rectangle contains the specified point, otherwise false.
*/
Rectangle.prototype.containsPoint = function (point) {
return point[0] >= this.x && point[0] <= this.x + this.width
&& point[1] >= this.y && point[1] <= this.y + this.height;
};
/**
*
* Indicates whether this rectangle intersects a specified one.
* @param {Rectangle} that The rectangle to test.
* @returns {Boolean} true if this triangle and the specified one intersect, otherwise false.
*/
Rectangle.prototype.intersects = function (that) {
if (that.x + that.width < this.x)
return false;
if (that.x > this.x + this.width)
return false;
if (that.y + that.height < this.y)
return false;
//noinspection RedundantIfStatementJS
if (that.y > this.y + this.height)
return false;
return true;
};
/**
* Indicates whether this rectangle intersects any rectangle in a specified array of rectangles.
* @param {Rectangle[]} rectangles The rectangles to test intersection with.
* @returns {Boolean} true if this rectangle intersects any rectangle in the array, otherwise false.
*/
Rectangle.prototype.intersectsRectangles = function (rectangles) {
if (rectangles) {
for (var i = 0; i < rectangles.length; i++) {
if (this.intersects(rectangles[i])) {
return true;
}
}
}
return false;
};
/**
* Returns a string representation of this object.
* @returns {String} A string representation of this object.
*/
Rectangle.prototype.toString = function () {
return this.x + ", " + this.y + ", " + this.width + ", " + this.height;
};
export default Rectangle;

View File

@ -0,0 +1,536 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Sector
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import Location from '../geom/Location';
import Logger from '../util/Logger';
import Vec3 from '../geom/Vec3';
import WWMath from '../util/WWMath';
/**
* Constructs a Sector from specified minimum and maximum latitudes and longitudes in degrees.
* @alias Sector
* @constructor
* @classdesc Represents a rectangular region in geographic coordinates in degrees.
* @param {Number} minLatitude The sector's minimum latitude in degrees.
* @param {Number} maxLatitude The sector's maximum latitude in degrees.
* @param {Number} minLongitude The sector's minimum longitude in degrees.
* @param {Number} maxLongitude The sector's maximum longitude in degrees.
*/
function Sector(minLatitude, maxLatitude, minLongitude, maxLongitude) {
/**
* This sector's minimum latitude in degrees.
* @type {Number}
*/
this.minLatitude = minLatitude;
/**
* This sector's maximum latitude in degrees.
* @type {Number}
*/
this.maxLatitude = maxLatitude;
/**
* This sector's minimum longitude in degrees.
* @type {Number}
*/
this.minLongitude = minLongitude;
/**
* This sector's maximum longitude in degrees.
* @type {Number}
*/
this.maxLongitude = maxLongitude;
}
/**
* A sector with minimum and maximum latitudes and minimum and maximum longitudes all zero.
* @constant
* @type {Sector}
*/
Sector.ZERO = new Sector(0, 0, 0, 0);
/**
* A sector that encompasses the full range of latitude ([-90, 90]) and longitude ([-180, 180]).
* @constant
* @type {Sector}
*/
Sector.FULL_SPHERE = new Sector(-WWMath.MAX_LAT, WWMath.MAX_LAT, -180, 180);
/**
* Sets this sector's latitudes and longitudes to those of a specified sector.
* @param {Sector} sector The sector to copy.
* @returns {Sector} This sector, set to the values of the specified sector.
* @throws {ArgumentError} If the specified sector is null or undefined.
*/
Sector.prototype.copy = function (sector) {
if (!sector) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "copy", "missingSector"));
}
this.minLatitude = sector.minLatitude;
this.maxLatitude = sector.maxLatitude;
this.minLongitude = sector.minLongitude;
this.maxLongitude = sector.maxLongitude;
return this;
};
/**
* Indicates whether this sector has width or height.
* @returns {Boolean} true if this sector's minimum and maximum latitudes or minimum and maximum
* longitudes do not differ, otherwise false.
*/
Sector.prototype.isEmpty = function () {
return this.minLatitude === this.maxLatitude && this.minLongitude === this.maxLongitude;
};
/**
* Returns the angle between this sector's minimum and maximum latitudes, in degrees.
* @returns {Number} The difference between this sector's minimum and maximum latitudes, in degrees.
*/
Sector.prototype.deltaLatitude = function () {
return this.maxLatitude - this.minLatitude;
};
/**
* Returns the angle between this sector's minimum and maximum longitudes, in degrees.
* @returns {Number} The difference between this sector's minimum and maximum longitudes, in degrees.
*/
Sector.prototype.deltaLongitude = function () {
return this.maxLongitude - this.minLongitude;
};
/**
* Returns the angle midway between this sector's minimum and maximum latitudes.
* @returns {Number} The mid-angle of this sector's minimum and maximum latitudes, in degrees.
*/
Sector.prototype.centroidLatitude = function () {
return 0.5 * (this.minLatitude + this.maxLatitude);
};
/**
* Returns the angle midway between this sector's minimum and maximum longitudes.
* @returns {Number} The mid-angle of this sector's minimum and maximum longitudes, in degrees.
*/
Sector.prototype.centroidLongitude = function () {
return 0.5 * (this.minLongitude + this.maxLongitude);
};
/**
* Computes the location of the angular center of this sector, which is the mid-angle of each of this sector's
* latitude and longitude dimensions.
* @param {Location} result A pre-allocated {@link Location} in which to return the computed centroid.
* @returns {Location} The specified result argument containing the computed centroid.
* @throws {ArgumentError} If the result argument is null or undefined.
*/
Sector.prototype.centroid = function (result) {
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "centroid", "missingResult"));
}
result.latitude = this.centroidLatitude();
result.longitude = this.centroidLongitude();
return result;
};
/**
* Returns this sector's minimum latitude in radians.
* @returns {Number} This sector's minimum latitude in radians.
*/
Sector.prototype.minLatitudeRadians = function () {
return this.minLatitude * Angle.DEGREES_TO_RADIANS;
};
/**
* Returns this sector's maximum latitude in radians.
* @returns {Number} This sector's maximum latitude in radians.
*/
Sector.prototype.maxLatitudeRadians = function () {
return this.maxLatitude * Angle.DEGREES_TO_RADIANS;
};
/**
* Returns this sector's minimum longitude in radians.
* @returns {Number} This sector's minimum longitude in radians.
*/
Sector.prototype.minLongitudeRadians = function () {
return this.minLongitude * Angle.DEGREES_TO_RADIANS;
};
/**
* Returns this sector's maximum longitude in radians.
* @returns {Number} This sector's maximum longitude in radians.
*/
Sector.prototype.maxLongitudeRadians = function () {
return this.maxLongitude * Angle.DEGREES_TO_RADIANS;
};
/**
* Modifies this sector to encompass an array of specified locations.
* @param {Location[]} locations An array of locations. The array may be sparse.
* @returns {Sector} This sector, modified to encompass all locations in the specified array.
* @throws {ArgumentError} If the specified array is null, undefined or empty or has fewer than two locations.
*/
Sector.prototype.setToBoundingSector = function (locations) {
if (!locations || locations.length < 2) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "setToBoundingSector",
"missingArray"));
}
var minLatitude = 90,
maxLatitude = -90,
minLongitude = 180,
maxLongitude = -180;
for (var idx = 0, len = locations.length; idx < len; idx += 1) {
var location = locations[idx];
if (!location) {
continue;
}
minLatitude = Math.min(minLatitude, location.latitude);
maxLatitude = Math.max(maxLatitude, location.latitude);
minLongitude = Math.min(minLongitude, location.longitude);
maxLongitude = Math.max(maxLongitude, location.longitude);
}
this.minLatitude = minLatitude;
this.maxLatitude = maxLatitude;
this.minLongitude = minLongitude;
this.maxLongitude = maxLongitude;
return this;
};
/**
* Computes bounding sectors from a list of locations that span the dateline.
* @param {Location[]} locations The locations to bound.
* @returns {Sector[]} Two sectors, one in the eastern hemisphere and one in the western hemisphere.
* Returns null if the computed bounding sector has zero width or height.
* @throws {ArgumentError} If the specified array is null, undefined or empty or the number of locations
* is less than 2.
*/
Sector.splitBoundingSectors = function (locations) {
if (!locations || locations.length < 2) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "splitBoundingSectors",
"missingArray"));
}
var minLat = 90;
var minLon = 180;
var maxLat = -90;
var maxLon = -180;
var lastLocation = null;
for (var idx = 0, len = locations.length; idx < len; idx += 1) {
var location = locations[idx];
var lat = location.latitude;
if (lat < minLat) {
minLat = lat;
}
if (lat > maxLat) {
maxLat = lat;
}
var lon = location.longitude;
if (lon >= 0 && lon < minLon) {
minLon = lon;
}
if (lon <= 0 && lon > maxLon) {
maxLon = lon;
}
if (lastLocation != null) {
var lastLon = lastLocation.longitude;
if (WWMath.signum(lon) != WWMath.signum(lastLon)) {
if (Math.abs(lon - lastLon) < 180) {
// Crossing the zero longitude line too
maxLon = 0;
minLon = 0;
}
}
}
lastLocation = location;
}
if (minLat === maxLat && minLon === maxLon) {
return null;
}
return [
new Sector(minLat, maxLat, minLon, 180), // Sector on eastern hemisphere.
new Sector(minLat, maxLat, -180, maxLon) // Sector on western hemisphere.
];
};
/**
* Indicates whether this sector intersects a specified sector.
* This sector intersects the specified sector when each sector's boundaries either overlap with the specified
* sector or are adjacent to the specified sector.
* The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
* [-180, 180] longitude).
* @param {Sector} sector The sector to test intersection with. May be null or undefined, in which case this
* function returns false.
* @returns {Boolean} true if the specifies sector intersections this sector, otherwise false.
*/
Sector.prototype.intersects = function (sector) {
// Assumes normalized angles: [-90, 90], [-180, 180].
return sector
&& this.minLongitude <= sector.maxLongitude
&& this.maxLongitude >= sector.minLongitude
&& this.minLatitude <= sector.maxLatitude
&& this.maxLatitude >= sector.minLatitude;
};
/**
* Indicates whether this sector intersects a specified sector exclusive of the sector boundaries.
* This sector overlaps the specified sector when the union of the two sectors defines a non-empty sector.
* The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
* [-180, 180] longitude).
* @param {Sector} sector The sector to test overlap with. May be null or undefined, in which case this
* function returns false.
* @returns {Boolean} true if the specified sector overlaps this sector, otherwise false.
*/
Sector.prototype.overlaps = function (sector) {
// Assumes normalized angles: [-90, 90], [-180, 180].
return sector
&& this.minLongitude < sector.maxLongitude
&& this.maxLongitude > sector.minLongitude
&& this.minLatitude < sector.maxLatitude
&& this.maxLatitude > sector.minLatitude;
};
/**
* Indicates whether this sector fully contains a specified sector.
* This sector contains the specified sector when the specified sector's boundaries are completely contained
* within this sector's boundaries, or are equal to this sector's boundaries.
* The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
* [-180, 180] longitude).
* @param {Sector} sector The sector to test containment with. May be null or undefined, in which case this
* function returns false.
* @returns {Boolean} true if the specified sector contains this sector, otherwise false.
*/
Sector.prototype.contains = function (sector) {
// Assumes normalized angles: [-90, 90], [-180, 180].
return sector
&& this.minLatitude <= sector.minLatitude
&& this.maxLatitude >= sector.maxLatitude
&& this.minLongitude <= sector.minLongitude
&& this.maxLongitude >= sector.maxLongitude;
};
/**
* Indicates whether this sector contains a specified geographic location.
* @param {Number} latitude The location's latitude in degrees.
* @param {Number} longitude The location's longitude in degrees.
* @returns {Boolean} true if this sector contains the location, otherwise false.
*/
Sector.prototype.containsLocation = function (latitude, longitude) {
// Assumes normalized angles: [-90, 90], [-180, 180].
return this.minLatitude <= latitude
&& this.maxLatitude >= latitude
&& this.minLongitude <= longitude
&& this.maxLongitude >= longitude;
};
/**
* Sets this sector to the intersection of itself and a specified sector.
* @param {Sector} sector The sector to intersect with this one.
* @returns {Sector} This sector, set to its intersection with the specified sector.
* @throws {ArgumentError} If the specified sector is null or undefined.
*/
Sector.prototype.intersection = function (sector) {
if (!sector instanceof Sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "intersection", "missingSector"));
}
// Assumes normalized angles: [-180, 180], [-90, 90].
if (this.minLatitude < sector.minLatitude)
this.minLatitude = sector.minLatitude;
if (this.maxLatitude > sector.maxLatitude)
this.maxLatitude = sector.maxLatitude;
if (this.minLongitude < sector.minLongitude)
this.minLongitude = sector.minLongitude;
if (this.maxLongitude > sector.maxLongitude)
this.maxLongitude = sector.maxLongitude;
// If the sectors do not overlap in either latitude or longitude, then the result of the above logic results in
// the max being greater than the min. In this case, set the max to indicate that the sector is empty in
// that dimension.
if (this.maxLatitude < this.minLatitude)
this.maxLatitude = this.minLatitude;
if (this.maxLongitude < this.minLongitude)
this.maxLongitude = this.minLongitude;
return this;
};
/**
* Returns a list of the Lat/Lon coordinates of a Sector's corners.
*
* @returns {Array} an array of the four corner locations, in the order SW, SE, NE, NW
*/
Sector.prototype.getCorners = function () {
var corners = [];
corners.push(new Location(this.minLatitude, this.minLongitude));
corners.push(new Location(this.minLatitude, this.maxLongitude));
corners.push(new Location(this.maxLatitude, this.maxLongitude));
corners.push(new Location(this.maxLatitude, this.minLongitude));
return corners;
};
/**
* Returns an array of {@link Vec3} that bounds the specified sector on the surface of the specified
* {@link Globe}. The returned points enclose the globe's surface terrain in the sector,
* according to the specified vertical exaggeration, minimum elevation, and maximum elevation. If the minimum and
* maximum elevation are equal, this assumes a maximum elevation of 10 + the minimum.
*
* @param {Globe} globe the globe the extent relates to.
* @param {Number} verticalExaggeration the globe's vertical surface exaggeration.
*
* @returns {Vec3} a set of points that enclose the globe's surface on the specified sector. Can be turned into a {@link BoundingBox}
* with the setToVec3Points method.
*
* @throws {ArgumentError} if the globe is null.
*/
Sector.prototype.computeBoundingPoints = function (globe, verticalExaggeration) {
// TODO: Refactor this method back to computeBoundingBox.
// This method was originally computeBoundingBox and returned a BoundingBox. This created a circular dependency between
// Sector and BoundingBox that the Karma unit test suite doesn't appear to like. If we discover a way to make Karma handle this
// situation, we should refactor this method.
if (globe === null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "computeBoundingBox", "missingGlobe"));
}
var minAndMaxElevations = globe.minAndMaxElevationsForSector(this);
// Compute the exaggerated minimum and maximum heights.
var minHeight = minAndMaxElevations[0] * verticalExaggeration;
var maxHeight = minAndMaxElevations[1] * verticalExaggeration;
if (minHeight === maxHeight)
maxHeight = minHeight + 10; // Ensure the top and bottom heights are not equal.
var points = [];
var corners = this.getCorners();
for (var i = 0; i < corners.length; i++) {
points.push(globe.computePointFromPosition(corners[i].latitude, corners[i].longitude, minHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(corners[i].latitude, corners[i].longitude, maxHeight, new Vec3(0, 0, 0)));
}
// A point at the centroid captures the maximum vertical dimension.
var centroid = this.centroid(new Location(0, 0));
points.push(globe.computePointFromPosition(centroid.latitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
// If the sector spans the equator, then the curvature of all four edges need to be taken into account. The
// extreme points along the top and bottom edges are located at their mid-points, and the extreme points along
// the left and right edges are on the equator. Add points with the longitude of the sector's centroid but with
// the sector's min and max latitude, and add points with the sector's min and max longitude but with latitude
// at the equator. See WWJINT-225.
if (this.minLatitude < 0 && this.maxLatitude > 0) {
points.push(globe.computePointFromPosition(this.minLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(this.maxLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(0, this.minLongitude, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(0, this.maxLongitude, maxHeight, new Vec3(0, 0, 0)));
}
// If the sector is located entirely in the southern hemisphere, then the curvature of its top edge needs to be
// taken into account. The extreme point along the top edge is located at its mid-point. Add a point with the
// longitude of the sector's centroid but with the sector's max latitude. See WWJINT-225.
else if (this.minLatitude < 0) {
points.push(globe.computePointFromPosition(this.maxLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
}
// If the sector is located entirely in the northern hemisphere, then the curvature of its bottom edge needs to
// be taken into account. The extreme point along the bottom edge is located at its mid-point. Add a point with
// the longitude of the sector's centroid but with the sector's min latitude. See WWJINT-225.
else {
points.push(globe.computePointFromPosition(this.minLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
}
// If the sector spans 360 degrees of longitude then is a band around the entire globe. (If one edge is a pole
// then the sector looks like a circle around the pole.) Add points at the min and max latitudes and longitudes
// 0, 180, 90, and -90 to capture full extent of the band.
if (this.deltaLongitude() >= 360) {
var minLat = this.minLatitude;
points.push(globe.computePointFromPosition(minLat, 0, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(minLat, 90, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(minLat, -90, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(minLat, 180, maxHeight, new Vec3(0, 0, 0)));
var maxLat = this.maxLatitude;
points.push(globe.computePointFromPosition(maxLat, 0, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(maxLat, 90, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(maxLat, -90, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(maxLat, 180, maxHeight, new Vec3(0, 0, 0)));
}
else if (this.deltaLongitude() > 180) {
// Need to compute more points to ensure the box encompasses the full sector.
var cLon = centroid.longitude;
var cLat = centroid.latitude;
// centroid latitude, longitude midway between min longitude and centroid longitude
var lon = (this.minLongitude + cLon) / 2;
points.push(globe.computePointFromPosition(cLat, lon, maxHeight, new Vec3(0, 0, 0)));
// centroid latitude, longitude midway between centroid longitude and max longitude
lon = (cLon + this.maxLongitude) / 2;
points.push(globe.computePointFromPosition(cLat, lon, maxHeight, new Vec3(0, 0, 0)));
// centroid latitude, longitude at min longitude and max longitude
points.push(globe.computePointFromPosition(cLat, this.minLongitude, maxHeight, new Vec3(0, 0, 0)));
points.push(globe.computePointFromPosition(cLat, this.maxLongitude, maxHeight, new Vec3(0, 0, 0)));
}
return points;
};
/**
* Sets this sector to the union of itself and a specified sector.
* @param {Sector} sector The sector to union with this one.
* @returns {Sector} This sector, set to its union with the specified sector.
* @throws {ArgumentError} if the specified sector is null or undefined.
*/
Sector.prototype.union = function (sector) {
if (!sector instanceof Sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "union", "missingSector"));
}
// Assumes normalized angles: [-180, 180], [-90, 90].
if (this.minLatitude > sector.minLatitude)
this.minLatitude = sector.minLatitude;
if (this.maxLatitude < sector.maxLatitude)
this.maxLatitude = sector.maxLatitude;
if (this.minLongitude > sector.minLongitude)
this.minLongitude = sector.minLongitude;
if (this.maxLongitude < sector.maxLongitude)
this.maxLongitude = sector.maxLongitude;
return this;
};
export default Sector;

View File

@ -0,0 +1,321 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Logger from '../util/Logger';
import ArgumentError from '../error/ArgumentError';
import Vec3 from '../geom/Vec3';
/**
* Constructs a two-component vector.
* @alias Vec2
* @classdesc Represents a two-component vector. Access the X component of the vector as v[0] and the Y
* component as v[1].
* @augments Float64Array
* @param {Number} x X component of vector.
* @param {Number} y Y component of vector.
* @constructor
*/
function Vec2(x, y) {
this[0] = x;
this[1] = y;
}
// Vec2 inherits from Float64Array.
Vec2.prototype = new Float64Array(2);
/**
* Assigns the components of this vector.
* @param {Number} x The X component of the vector.
* @param {Number} y The Y component of the vector.
* @returns {Vec2} This vector with the specified components assigned.
*/
Vec2.prototype.set = function (x, y) {
this[0] = x;
this[1] = y;
return this;
};
/**
* Copies the components of a specified vector to this vector.
* @param {Vec2} vector The vector to copy.
* @returns {Vec2} This vector set to the values of the specified vector.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec2.prototype.copy = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "copy", "missingVector"));
}
this[0] = vector[0];
this[1] = vector[1];
return this;
};
/**
* Indicates whether the X and Y components of this vector are identical to those of a specified vector.
* @param {Vec2} vector The vector to test.
* @returns {Boolean} true if this vector's components are equal to those of the specified vector,
* otherwise false.
*/
Vec2.prototype.equals = function (vector) {
return this[0] === vector[0] && this[1] === vector[1];
};
/**
* Computes the average of a specified array of vectors.
* @param {Vec2[]} vectors The vectors whose average to compute.
* @param {Vec2} result A pre-allocated Vec2 in which to return the computed average.
* @returns {Vec2} The result argument set to the average of the specified lists of vectors.
* @throws {ArgumentError} If the specified array of vectors is null, undefined or empty, or the specified
* result argument is null or undefined.
*/
Vec2.average = function (vectors, result) {
if (!vectors || vectors.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "average", "missingArray"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "average", "missingResult"));
}
var count = vectors.length,
vec;
result[0] = 0;
result[1] = 0;
for (var i = 0, len = vectors.length; i < len; i++) {
vec = vectors[i];
result[0] += vec[0] / count;
result[1] += vec[1] / count;
}
return result;
};
/**
* Adds a vector to this vector.
* @param {Vec2} addend The vector to add to this one.
* @returns {Vec2} This vector after adding the specified vector to it.
* @throws {ArgumentError} If the specified addend is null or undefined.
*/
Vec2.prototype.add = function (addend) {
if (!addend) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "add", "missingVector"));
}
this[0] += addend[0];
this[1] += addend[1];
return this;
};
/**
* Subtracts a vector from this vector.
* @param {Vec2} subtrahend The vector to subtract from this one.
* @returns {Vec2} This vector after subtracting the specified vector from it.
* @throws {ArgumentError} If the subtrahend is null or undefined.
*/
Vec2.prototype.subtract = function (subtrahend) {
if (!subtrahend) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "subtract", "missingVector"));
}
this[0] -= subtrahend[0];
this[1] -= subtrahend[1];
return this;
};
/**
* Multiplies this vector by a scalar.
* @param {Number} scalar The scalar to multiply this vector by.
* @returns {Vec2} This vector multiplied by the specified scalar.
*/
Vec2.prototype.multiply = function (scalar) {
this[0] *= scalar;
this[1] *= scalar;
return this;
};
/**
* Divide this vector by a scalar.
* @param {Number} divisor The scalar to divide this vector by.
* @returns {Vec2} This vector divided by the specified scalar.
*/
Vec2.prototype.divide = function (divisor) {
this[0] /= divisor;
this[1] /= divisor;
return this;
};
/**
* Mixes (interpolates) a specified vector with this vector, modifying this vector.
* @param {Vec2} vector The vector to mix.
* @param {Number} weight The relative weight of this vector.
* @returns {Vec2} This vector modified to the mix of itself and the specified vector.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec2.prototype.mix = function (vector, weight) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "mix", "missingVector"));
}
var w0 = 1 - weight,
w1 = weight;
this[0] = this[0] * w0 + vector[0] * w1;
this[1] = this[1] * w0 + vector[1] * w1;
return this;
};
/**
* Negates this vector.
* @returns {Vec2} This vector, negated.
*/
Vec2.prototype.negate = function () {
this[0] = -this[0];
this[1] = -this[1];
return this;
};
/**
* Computes the scalar dot product of this vector and a specified vector.
* @param {Vec2} vector The vector to multiply.
* @returns {Number} The scalar dot product of the vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec2.prototype.dot = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "dot", "missingVector"));
}
return this[0] * vector[0] + this[1] * vector[1];
};
/**
* Computes the squared magnitude of this vector.
* @returns {Number} The squared magnitude of this vector.
*/
Vec2.prototype.magnitudeSquared = function () {
return this.dot(this);
};
/**
* Computes the magnitude of this vector.
* @returns {Number} The magnitude of this vector.
*/
Vec2.prototype.magnitude = function () {
return Math.sqrt(this.magnitudeSquared());
};
/**
* Normalizes this vector to a unit vector.
* @returns {Vec2} This vector, normalized.
*/
Vec2.prototype.normalize = function () {
var magnitude = this.magnitude(),
magnitudeInverse = 1 / magnitude;
this[0] *= magnitudeInverse;
this[1] *= magnitudeInverse;
return this;
};
/**
* Computes the squared distance from this vector to a specified vector.
* @param {Vec2} vector The vector to compute the distance to.
* @returns {Number} The squared distance between the vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec2.prototype.distanceToSquared = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "distanceToSquared", "missingVector"));
}
var dx = this[0] - vector[0],
dy = this[1] - vector[1];
return dx * dx + dy * dy;
};
/**
* Computes the distance from this vector to a specified vector.
* @param {Vec2} vector The vector to compute the distance to.
* @returns {Number} The distance between the vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec2.prototype.distanceTo = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "distanceTo", "missingVector"));
}
return Math.sqrt(this.distanceToSquared(vector));
};
/**
* Creates a {@link Vec3} using this vector's X and Y components and a Z component of 0.
* @returns {Vec3} A new vector whose X and Y components are those of this vector and whose Z component is 0.
*/
Vec2.prototype.toVec3 = function () {
return new Vec3(this[0], this[1], 0);
};
/**
* Swaps the components of this vector with those of another vector. This vector is set to the values of the
* specified vector, and the specified vector's components are set to the values of this vector.
* @param {Vec2} that The vector to swap.
* @returns {Vec2} This vector set to the values of the specified vector.
*/
Vec2.prototype.swap = function (that) {
var tmp = this[0];
this[0] = that[0];
that[0] = tmp;
tmp = this[1];
this[1] = that[1];
that[1] = tmp;
return this;
};
/**
* Returns a string representation of this vector.
* @returns {String} A string representation of this vector, in the form "(x, y)".
*/
Vec2.prototype.toString = function () {
return "(" + this[0] + ", " + this[1] + ")";
};
export default Vec2;

View File

@ -0,0 +1,538 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Logger from '../util/Logger';
import ArgumentError from '../error/ArgumentError';
/**
* Constructs a three-component vector.
* @alias Vec3
* @classdesc Represents a three-component vector. Access the X component of the vector as v[0], the Y
* component as v[1] and the Z component as v[2].
* @augments Float64Array
* @param {Number} x X component of vector.
* @param {Number} y Y component of vector.
* @param {Number} z Z component of vector.
* @constructor
*/
function Vec3(x, y, z) {
this[0] = x;
this[1] = y;
this[2] = z;
}
// Vec3 extends Float64Array.
Vec3.prototype = new Float64Array(3);
/**
* A vector corresponding to the origin.
* @type {Vec3}
*/
Vec3.ZERO = new Vec3(0, 0, 0);
/**
* Computes the average of a specified array of vectors.
* @param {Vec3[]} vectors The vectors whose average to compute.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed average.
* @returns {Vec3} The result argument set to the average of the specified array of vectors.
* @throws {ArgumentError} If the specified array of vectors is null, undefined or empty or the specified
* result argument is null or undefined.
*/
Vec3.average = function (vectors, result) {
if (!vectors || vectors.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "average", "missingArray"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "average", "missingResult"));
}
var count = vectors.length,
vec;
result[0] = 0;
result[1] = 0;
result[2] = 0;
for (var i = 0, len = vectors.length; i < len; i++) {
vec = vectors[i];
result[0] += vec[0] / count;
result[1] += vec[1] / count;
result[2] += vec[2] / count;
}
return result;
};
/**
* Computes the average of a specified array of points packed into a single array.
* @param {Float32Array | Float64Array | Number[]} points The points whose average to compute.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed average.
* @returns {Vec3} The result argument set to the average of the specified array of points.
* @throws {ArgumentError} If the specified array of points is null, undefined or empty or the result argument
* is null or undefined.
*/
Vec3.averageOfBuffer = function (points, result) {
if (!points || points.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "averageBuffer", "missingArray"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "averageBuffer", "missingResult"));
}
var count = points.length / 3;
result[0] = 0;
result[1] = 0;
result[2] = 0;
for (var i = 0; i < count; i++) {
result[0] += points[i * 3] / count;
result[1] += points[i * 3 + 1] / count;
result[2] += points[i * 3 + 2] / count;
}
return result;
};
/**
* Indicates whether three vectors are colinear.
* @param {Vec3} a The first vector.
* @param {Vec3} b The second vector.
* @param {Vec3} c The third vector.
* @returns {Boolean} true if the vectors are colinear, otherwise false.
* @throws {ArgumentError} If any of the specified vectors are null or undefined.
*/
Vec3.areColinear = function (a, b, c) {
if (!a || !b || !c) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "areColinear", "missingVector"));
}
var ab = new Vec3(a[0] - b[0], a[1] - b[1], a[2] - b[2]).normalize(),
bc = new Vec3(c[0] - b[0], c[1] - b[1], c[2] - b[2]).normalize();
// ab and bc are considered colinear if their dot product is near +/-1.
return Math.abs(ab.dot(bc)) > 0.999;
};
/**
* Computes the normal vector of a specified triangle.
*
* @param {Vec3} a The triangle's first vertex.
* @param {Vec3} b The triangle's second vertex.
* @param {Vec3} c The triangle's third vertex.
* @returns {Vec3} The triangle's unit-normal vector.
* @throws {ArgumentError} If any of the specified vectors are null or undefined.
*/
Vec3.computeTriangleNormal = function (a, b, c) {
if (!a || !b || !c) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "areColinear", "missingVector"));
}
var x = (b[1] - a[1]) * (c[2] - a[2]) - (b[2] - a[2]) * (c[1] - a[1]),
y = (b[2] - a[2]) * (c[0] - a[0]) - (b[0] - a[0]) * (c[2] - a[2]),
z = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]),
length = x * x + y * y + z * z;
if (length === 0) {
return new Vec3(x, y, z);
}
length = Math.sqrt(length);
return new Vec3(x / length, y / length, z / length);
};
/**
* Finds three non-colinear points in an array of coordinates.
*
* @param {Number[]} coords The coordinates, in the order x0, y0, z0, x1, y1, z1, ...
* @param {Number} stride The number of numbers between successive points. 0 indicates that the points
* are arranged one immediately after the other, as would the value 3.
* @returns {Vec3[]} Three non-colinear points from the input array of coordinates, or null if three
* non-colinear points could not be found or the specified coordinates array is null, undefined or
* contains fewer than three points.
*/
Vec3.findThreeIndependentVertices = function (coords, stride) {
var xstride = stride && stride > 0 ? stride : 3;
if (!coords || coords.length < 3 * xstride) {
return null;
}
var a = new Vec3(coords[0], coords[1], coords[2]),
b = null,
c = null,
k = xstride;
for (; k < coords.length; k += xstride) {
b = new Vec3(coords[k], coords[k + 1], coords[k + 2]);
if (!(b[0] === a[0] && b[1] === a[1] && b[2] === a[2])) {
break;
}
b = null;
}
if (!b) {
return null;
}
for (k += xstride; k < coords.length; k += xstride) {
c = new Vec3(coords[k], coords[k + 1], coords[k + 2]);
// if c is not coincident with a or b, and the vectors ab and bc are not colinear, break and
// return a, b, c.
if (!(c[0] === a[0] && c[1] === a[1] && c[2] === a[2]
|| c[0] === b[0] && c[1] === b[1] && c[2] === b[2])) {
if (!Vec3.areColinear(a, b, c))
break;
}
c = null;
}
return c ? [a, b, c] : null;
};
/**
* Computes a unit-normal vector for a buffer of coordinate triples. The normal vector is computed from the
* first three non-colinear points in the buffer.
*
* @param {Number[]} coords The coordinates, in the order x0, y0, z0, x1, y1, z1, ...
* @param {Number} stride The number of numbers between successive points. 0 indicates that the points
* are arranged one immediately after the other, as would the value 3.
* @returns {Vec3} The computed unit-length normal vector.
*/
Vec3.computeBufferNormal = function (coords, stride) {
var vertices = Vec3.findThreeIndependentVertices(coords, stride);
return vertices ? Vec3.computeTriangleNormal(vertices[0], vertices[1], vertices[2]) : null;
};
/**
* Assigns the components of this vector.
* @param {Number} x The X component of the vector.
* @param {Number} y The Y component of the vector.
* @param {Number} z The Z component of the vector.
* @returns {Vec3} This vector with the specified components assigned.
*/
Vec3.prototype.set = function (x, y, z) {
this[0] = x;
this[1] = y;
this[2] = z;
return this;
};
/**
* Copies the components of a specified vector to this vector.
* @param {Vec3} vector The vector to copy.
* @returns {Vec3} This vector set to the X, Y and Z values of the specified vector.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.copy = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "copy", "missingVector"));
}
this[0] = vector[0];
this[1] = vector[1];
this[2] = vector[2];
return this;
};
/**
* Indicates whether the components of this vector are identical to those of a specified vector.
* @param {Vec3} vector The vector to test.
* @returns {Boolean} true if the components of this vector are equal to those of the specified one,
* otherwise false.
*/
Vec3.prototype.equals = function (vector) {
return this[0] === vector[0] && this[1] === vector[1] && this[2] === vector[2];
};
/**
* Adds a specified vector to this vector.
* @param {Vec3} addend The vector to add.
* @returns {Vec3} This vector after adding the specified vector to it.
* @throws {ArgumentError} If the addend is null or undefined.
*/
Vec3.prototype.add = function (addend) {
if (!addend) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "add", "missingVector"));
}
this[0] += addend[0];
this[1] += addend[1];
this[2] += addend[2];
return this;
};
/**
* Subtracts a specified vector from this vector.
* @param {Vec3} subtrahend The vector to subtract
* @returns {Vec3} This vector after subtracting the specified vector from it.
* @throws {ArgumentError} If the subtrahend is null or undefined.
*/
Vec3.prototype.subtract = function (subtrahend) {
if (!subtrahend) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "subtract", "missingVector"));
}
this[0] -= subtrahend[0];
this[1] -= subtrahend[1];
this[2] -= subtrahend[2];
return this;
};
/**
* Multiplies this vector by a scalar.
* @param {Number} scalar The scalar to multiply this vector by.
* @returns {Vec3} This vector multiplied by the specified scalar.
*/
Vec3.prototype.multiply = function (scalar) {
this[0] *= scalar;
this[1] *= scalar;
this[2] *= scalar;
return this;
};
/**
* Divides this vector by a scalar.
* @param {Number} divisor The scalar to divide this vector by.
* @returns {Vec3} This vector divided by the specified scalar.
*/
Vec3.prototype.divide = function (divisor) {
this[0] /= divisor;
this[1] /= divisor;
this[2] /= divisor;
return this;
};
/**
* Multiplies this vector by a 4x4 matrix. The multiplication is performed with an implicit W component of 1.
* The resultant W component of the product is then divided through the X, Y, and Z components.
*
* @param {Matrix} matrix The matrix to multiply this vector by.
* @returns {Vec3} This vector multiplied by the specified matrix.
* @throws ArgumentError If the specified matrix is null or undefined.
*/
Vec3.prototype.multiplyByMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "multiplyByMatrix", "missingMatrix"));
}
var x = matrix[0] * this[0] + matrix[1] * this[1] + matrix[2] * this[2] + matrix[3],
y = matrix[4] * this[0] + matrix[5] * this[1] + matrix[6] * this[2] + matrix[7],
z = matrix[8] * this[0] + matrix[9] * this[1] + matrix[10] * this[2] + matrix[11],
w = matrix[12] * this[0] + matrix[13] * this[1] + matrix[14] * this[2] + matrix[15];
this[0] = x / w;
this[1] = y / w;
this[2] = z / w;
return this;
};
/**
* Mixes (interpolates) a specified vector with this vector, modifying this vector.
* @param {Vec3} vector The vector to mix with this one.
* @param {Number} weight The relative weight of this vector.
* @returns {Vec3} This vector modified to the mix of itself and the specified vector.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.mix = function (vector, weight) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "mix", "missingVector"));
}
var w0 = 1 - weight,
w1 = weight;
this[0] = this[0] * w0 + vector[0] * w1;
this[1] = this[1] * w0 + vector[1] * w1;
this[2] = this[2] * w0 + vector[2] * w1;
return this;
};
/**
* Negates the components of this vector.
* @returns {Vec3} This vector, negated.
*/
Vec3.prototype.negate = function () {
this[0] = -this[0];
this[1] = -this[1];
this[2] = -this[2];
return this;
};
/**
* Computes the scalar dot product of this vector and a specified vector.
* @param {Vec3} vector The vector to multiply.
* @returns {Number} The dot product of the two vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.dot = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "dot", "missingVector"));
}
return this[0] * vector[0] +
this[1] * vector[1] +
this[2] * vector[2];
};
/**
* Computes the cross product of this vector and a specified vector, modifying this vector.
* @param {Vec3} vector The vector to cross with this vector.
* @returns {Vec3} This vector set to the cross product of itself and the specified vector.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.cross = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "cross", "missingVector"));
}
var x = this[1] * vector[2] - this[2] * vector[1],
y = this[2] * vector[0] - this[0] * vector[2],
z = this[0] * vector[1] - this[1] * vector[0];
this[0] = x;
this[1] = y;
this[2] = z;
return this;
};
/**
* Computes the squared magnitude of this vector.
* @returns {Number} The squared magnitude of this vector.
*/
Vec3.prototype.magnitudeSquared = function () {
return this.dot(this);
};
/**
* Computes the magnitude of this vector.
* @returns {Number} The magnitude of this vector.
*/
Vec3.prototype.magnitude = function () {
return Math.sqrt(this.magnitudeSquared());
};
/**
* Normalizes this vector to a unit vector.
* @returns {Vec3} This vector, normalized.
*/
Vec3.prototype.normalize = function () {
var magnitude = this.magnitude(),
magnitudeInverse = 1 / magnitude;
this[0] *= magnitudeInverse;
this[1] *= magnitudeInverse;
this[2] *= magnitudeInverse;
return this;
};
/**
* Computes the squared distance from this vector to a specified vector.
* @param {Vec3} vector The vector to compute the distance to.
* @returns {Number} The squared distance between the vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.distanceToSquared = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "distanceToSquared", "missingVector"));
}
var dx = this[0] - vector[0],
dy = this[1] - vector[1],
dz = this[2] - vector[2];
return dx * dx + dy * dy + dz * dz;
};
/**
* Computes the distance from this vector to another vector.
* @param {Vec3} vector The vector to compute the distance to.
* @returns {number} The distance between the vectors.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
Vec3.prototype.distanceTo = function (vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "distanceTo", "missingVector"));
}
return Math.sqrt(this.distanceToSquared(vector));
};
/**
* Swaps this vector with that vector. This vector's components are set to the values of the specified
* vector's components, and the specified vector's components are set to the values of this vector's components.
* @param {Vec3} that The vector to swap.
* @returns {Vec3} This vector set to the values of the specified vector.
*/
Vec3.prototype.swap = function (that) {
var tmp = this[0];
this[0] = that[0];
that[0] = tmp;
tmp = this[1];
this[1] = that[1];
that[1] = tmp;
tmp = this[2];
this[2] = that[2];
that[2] = tmp;
return this;
};
/**
* Returns a string representation of this vector.
* @returns {String} A string representation of this vector, in the form "(x, y, z)".
*/
Vec3.prototype.toString = function () {
return "(" + this[0] + ", " + this[1] + ", " + this[2] + ")";
};
export default Vec3;

View File

@ -0,0 +1,162 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ClickRecognizer
*/
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a mouse click gesture recognizer.
* @alias ClickRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for single or multiple mouse clicks.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function ClickRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
/**
*
* @type {Number}
*/
this.numberOfClicks = 1;
/**
*
* @type {Number}
*/
this.button = 0;
// Intentionally not documented.
this.maxMouseMovement = 5;
// Intentionally not documented.
this.maxClickDuration = 500;
// Intentionally not documented.
this.maxClickInterval = 400;
// Intentionally not documented.
this.clicks = [];
// Intentionally not documented.
this.timeout = null;
}
ClickRecognizer.prototype = Object.create(GestureRecognizer.prototype);
// Documented in superclass.
ClickRecognizer.prototype.reset = function () {
GestureRecognizer.prototype.reset.call(this);
this.clicks = [];
this.cancelFailAfterDelay();
};
// Documented in superclass.
ClickRecognizer.prototype.mouseDown = function (event) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
if (this.button != event.button) {
this.state = WorldWind.FAILED;
} else {
var click = {
clientX: this.clientX,
clientY: this.clientY
};
this.clicks.push(click);
this.failAfterDelay(this.maxClickDuration); // fail if the click is down too long
}
};
// Documented in superclass.
ClickRecognizer.prototype.mouseMove = function (event) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
var dx = this.translationX,
dy = this.translationY,
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > this.maxMouseMovement) {
this.state = WorldWind.FAILED;
}
};
// Documented in superclass.
ClickRecognizer.prototype.mouseUp = function (event) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
if (this.mouseButtonMask != 0) {
return; // wait until the last button is up
}
var clickCount = this.clicks.length;
if (clickCount == this.numberOfClicks) {
this.clientX = this.clicks[0].clientX;
this.clientY = this.clicks[0].clientY;
this.state = WorldWind.RECOGNIZED;
} else {
this.failAfterDelay(this.maxClickInterval); // fail if the interval between clicks is too long
}
};
// Documented in superclass.
ClickRecognizer.prototype.touchStart = function (touch) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
this.state = WorldWind.FAILED; // mouse gestures fail upon receiving a touch event
};
// Intentionally not documented.
ClickRecognizer.prototype.failAfterDelay = function (delay) {
var self = this;
if (self.timeout) {
window.clearTimeout(self.timeout);
}
self.timeout = window.setTimeout(function () {
self.timeout = null;
if (self.state == WorldWind.POSSIBLE) {
self.state = WorldWind.FAILED; // fail if we haven't already reached a terminal state
}
}, delay);
};
// Intentionally not documented.
ClickRecognizer.prototype.cancelFailAfterDelay = function () {
var self = this;
if (self.timeout) {
window.clearTimeout(self.timeout);
self.timeout = null;
}
};
export default ClickRecognizer;

View File

@ -0,0 +1,108 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports DragRecognizer
*/
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a mouse drag gesture recognizer.
* @alias DragRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for mouse drag gestures.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function DragRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
/**
*
* @type {Number}
*/
this.button = 0;
// Intentionally not documented.
this.interpretDistance = 5;
}
DragRecognizer.prototype = Object.create(GestureRecognizer.prototype);
// Documented in superclass.
DragRecognizer.prototype.mouseMove = function (event) {
if (this.state == WorldWind.POSSIBLE) {
if (this.shouldInterpret()) {
if (this.shouldRecognize()) {
this.translationX = 0; // set translation to zero when the drag begins
this.translationY = 0;
this.state = WorldWind.BEGAN;
} else {
this.state = WorldWind.FAILED;
}
}
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.CHANGED;
}
};
// Documented in superclass.
DragRecognizer.prototype.mouseUp = function (event) {
if (this.mouseButtonMask == 0) { // last button up
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.ENDED;
}
}
};
// Documented in superclass.
DragRecognizer.prototype.touchStart = function (touch) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED; // mouse gestures fail upon receiving a touch event
}
};
/**
*
* @returns {Boolean}
* @protected
*/
DragRecognizer.prototype.shouldInterpret = function () {
var dx = this.translationX,
dy = this.translationY,
distance = Math.sqrt(dx * dx + dy * dy);
return distance > this.interpretDistance; // interpret mouse movement when the cursor moves far enough
};
/**
*
* @returns {Boolean}
* @protected
*/
DragRecognizer.prototype.shouldRecognize = function () {
var buttonBit = 1 << this.button;
return buttonBit == this.mouseButtonMask; // true when the specified button is the only button down
};
export default DragRecognizer;

View File

@ -0,0 +1,793 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GestureRecognizer
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Touch from '../gesture/Touch';
/**
* Constructs a base gesture recognizer. This is an abstract base class and not intended to be instantiated
* directly.
* @alias GestureRecognizer
* @constructor
* @classdesc Gesture recognizers translate user input event streams into higher level actions. A gesture
* recognizer is associated with an event target, which dispatches mouse and keyboard events to the gesture
* recognizer. When a gesture recognizer has received enough information from the event stream to interpret the
* action, it calls its callback functions. Callback functions may be specified at construction or added to the
* [gestureCallbacks]{@link GestureRecognizer#gestureCallbacks} list after construction.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
// TODO: evaluate target usage
function GestureRecognizer(target, callback) {
if (!target) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "constructor", "missingTarget"));
}
/**
* Indicates the document element this gesture recognizer observes for UI events.
* @type {EventTarget}
* @readonly
*/
this.target = target;
/**
* Indicates whether or not this gesture recognizer is enabled. When false, this gesture recognizer will
* ignore any events dispatched by its target.
* @type {Boolean}
* @default true
*/
this.enabled = true;
// Documented with its property accessor below.
this._state = WorldWind.POSSIBLE;
// Intentionally not documented.
this._nextState = null;
// Documented with its property accessor below.
this._clientX = 0;
// Documented with its property accessor below.
this._clientY = 0;
// Intentionally not documented.
this._clientStartX = 0;
// Intentionally not documented.
this._clientStartY = 0;
// Documented with its property accessor below.
this._translationX = 0;
// Documented with its property accessor below.
this._translationY = 0;
// Intentionally not documented.
this._translationWeight = 0.4;
// Documented with its property accessor below.
this._mouseButtonMask = 0;
// Intentionally not documented.
this._touches = [];
// Intentionally not documented.
this._touchCentroidShiftX = 0;
// Intentionally not documented.
this._touchCentroidShiftY = 0;
// Documented with its property accessor below.
this._gestureCallbacks = [];
// Intentionally not documented.
this._canRecognizeWith = [];
// Intentionally not documented.
this._requiresFailureOf = [];
// Intentionally not documented.
this._requiredToFailBy = [];
// Add the optional gesture callback.
if (callback) {
this._gestureCallbacks.push(callback);
}
// Intentionally not documented.
this.listenerList = [];
// Add this recognizer to the list of all recognizers.
GestureRecognizer.allRecognizers.push(this);
}
// Intentionally not documented.
GestureRecognizer.allRecognizers = [];
Object.defineProperties(GestureRecognizer.prototype, {
/**
* Indicates this gesture's current state. Possible values are WorldWind.POSSIBLE, WorldWind.FAILED,
* WorldWind.RECOGNIZED, WorldWind.BEGAN, WorldWind.CHANGED, WorldWind.CANCELLED and WorldWind.ENDED.
* @type {String}
* @default WorldWind.POSSIBLE
* @memberof GestureRecognizer.prototype
*/
state: {
get: function () {
return this._state;
},
set: function (value) {
this.transitionToState(value);
}
},
/**
* Indicates the X coordinate of this gesture.
* @type {Number}
* @memberof GestureRecognizer.prototype
*/
clientX: {
get: function () {
return this._clientX;
},
set: function (value) {
this._clientX = value;
}
},
/**
* Returns the Y coordinate of this gesture.
* @type {Number}
* @memberof GestureRecognizer.prototype
*/
clientY: {
get: function () {
return this._clientY;
},
set: function (value) {
this._clientY = value;
}
},
/**
* Indicates this gesture's translation along the X axis since the gesture started.
* @type {Number}
* @memberof GestureRecognizer.prototype
*/
translationX: {
get: function () {
return this._translationX;
},
set: function (value) {
this._translationX = value;
this._clientStartX = this._clientX;
this._touchCentroidShiftX = 0;
}
},
/**
* Indicates this gesture's translation along the Y axis since the gesture started.
* @type {Number}
* @memberof GestureRecognizer.prototype
*/
translationY: {
get: function () {
return this._translationY;
},
set: function (value) {
this._translationY = value;
this._clientStartY = this._clientY;
this._touchCentroidShiftY = 0;
}
},
/**
* Indicates the currently pressed mouse buttons as a bitmask. A value of 0 indicates that no buttons are
* pressed. A nonzero value indicates that one or more buttons are pressed as follows: bit 1 indicates the
* primary button, bit 2 indicates the the auxiliary button, bit 3 indicates the secondary button.
* @type {Number}
* @readonly
* @memberof GestureRecognizer.prototype
*/
mouseButtonMask: {
get: function () {
return this._mouseButtonMask;
}
},
/**
* Indicates the number of active touches.
* @type {Number}
* @readonly
* @memberof GestureRecognizer.prototype
*/
touchCount: {
get: function () {
return this._touches.length;
}
},
/**
* The list of functions to call when this gesture is recognized. The functions have a single argument:
* this gesture recognizer, e.g., <code>gestureCallback(recognizer)</code>. Applications may
* add functions to this array or remove them.
* @type {Function[]}
* @readonly
* @memberof GestureRecognizer.prototype
*/
gestureCallbacks: {
get: function () {
return this._gestureCallbacks;
}
}
});
/**
*
* @param index
* @returns {Touch}
* @throws {ArgumentError} If the index is out of range.
*/
GestureRecognizer.prototype.touch = function (index) {
if (index < 0 || index >= this._touches.length) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "touch", "indexOutOfRange"));
}
return this._touches[index];
};
/**
*
* @param recognizer
*/
GestureRecognizer.prototype.recognizeSimultaneouslyWith = function (recognizer) {
if (!recognizer) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "recognizeSimultaneouslyWith",
"The specified gesture recognizer is null or undefined."));
}
var index = this._canRecognizeWith.indexOf(recognizer);
if (index == -1) {
this._canRecognizeWith.push(recognizer);
recognizer._canRecognizeWith.push(this);
}
};
/**
*
* @param recognizer
* @returns {Boolean}
*/
GestureRecognizer.prototype.canRecognizeSimultaneouslyWith = function (recognizer) {
var index = this._canRecognizeWith.indexOf(recognizer);
return index != -1;
};
/**
*
* @param recognizer
*/
GestureRecognizer.prototype.requireRecognizerToFail = function (recognizer) {
if (!recognizer) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "requireRecognizerToFail",
"The specified gesture recognizer is null or undefined"));
}
var index = this._requiresFailureOf.indexOf(recognizer);
if (index == -1) {
this._requiresFailureOf.push(recognizer);
recognizer._requiredToFailBy.push(this);
}
};
/**
*
* @param recognizer
* @returns {Boolean}
*/
GestureRecognizer.prototype.requiresRecognizerToFail = function (recognizer) {
var index = this._requiresFailureOf.indexOf(recognizer);
return index != -1;
};
/**
*
* @param recognizer
* @returns {Boolean}
*/
GestureRecognizer.prototype.requiredToFailByRecognizer = function (recognizer) {
var index = this._requiredToFailBy.indexOf(recognizer);
return index != -1;
};
/**
* @protected
*/
GestureRecognizer.prototype.reset = function () {
this._state = WorldWind.POSSIBLE;
this._nextState = null;
this._clientX = 0;
this._clientY = 0;
this._clientStartX = 0;
this._clientStartY = 0;
this._translationX = 0;
this._translationY = 0;
this._mouseButtonMask = 0;
this._touches = [];
this._touchCentroidShiftX = 0;
this._touchCentroidShiftY = 0;
};
/**
* @protected
*/
GestureRecognizer.prototype.prepareToRecognize = function () {
};
/**
*
* @param event
* @protected
*/
GestureRecognizer.prototype.mouseDown = function (event) {
};
/**
*
* @param event
* @protected
*/
GestureRecognizer.prototype.mouseMove = function (event) {
};
/**
*
* @param event
* @protected
*/
GestureRecognizer.prototype.mouseUp = function (event) {
};
/**
*
* @param touch
* @protected
*/
GestureRecognizer.prototype.touchStart = function (touch) {
};
/**
*
* @param touch
* @protected
*/
GestureRecognizer.prototype.touchMove = function (touch) {
};
/**
*
* @param touch
* @protected
*/
GestureRecognizer.prototype.touchCancel = function (touch) {
};
/**
*
* @param touch
* @protected
*/
GestureRecognizer.prototype.touchEnd = function (touch) {
};
// Intentionally not documented.
GestureRecognizer.prototype.transitionToState = function (newState) {
this._nextState = null; // clear any pending state transition
if (newState === WorldWind.FAILED) {
this._state = newState;
this.updateRecognizersWaitingForFailure();
this.resetIfEventsEnded();
} else if (newState === WorldWind.RECOGNIZED) {
this.tryToRecognize(newState); // may prevent the transition to Recognized
if (this._state === newState) {
this.prepareToRecognize();
this.notifyListeners();
this.callGestureCallbacks();
this.resetIfEventsEnded();
}
} else if (newState === WorldWind.BEGAN) {
this.tryToRecognize(newState); // may prevent the transition to Began
if (this._state === newState) {
this.prepareToRecognize();
this.notifyListeners();
this.callGestureCallbacks();
}
} else if (newState === WorldWind.CHANGED) {
this._state = newState;
this.notifyListeners();
this.callGestureCallbacks();
} else if (newState === WorldWind.CANCELLED) {
this._state = newState;
this.notifyListeners();
this.callGestureCallbacks();
this.resetIfEventsEnded();
} else if (newState === WorldWind.ENDED) {
this._state = newState;
this.notifyListeners();
this.callGestureCallbacks();
this.resetIfEventsEnded();
}
};
// Intentionally not documented.
GestureRecognizer.prototype.updateRecognizersWaitingForFailure = function () {
// Transition gestures that are waiting for this gesture to transition to Failed.
for (var i = 0, len = this._requiredToFailBy.length; i < len; i++) {
var recognizer = this._requiredToFailBy[i];
if (recognizer._nextState != null) {
recognizer.transitionToState(recognizer._nextState);
}
}
};
// Intentionally not documented.
GestureRecognizer.prototype.tryToRecognize = function (newState) {
// Transition to Failed if another gesture can prevent this gesture from recognizing.
if (GestureRecognizer.allRecognizers.some(this.canBePreventedByRecognizer, this)) {
this.transitionToState(WorldWind.FAILED);
return;
}
// Delay the transition to Recognized/Began if this gesture is waiting for a gesture in the Possible state.
if (GestureRecognizer.allRecognizers.some(this.isWaitingForRecognizerToFail, this)) {
this._nextState = newState;
return;
}
// Transition to Failed all other gestures that can be prevented from recognizing by this gesture.
var prevented = GestureRecognizer.allRecognizers.filter(this.canPreventRecognizer, this);
for (var i = 0, len = prevented.length; i < len; i++) {
prevented[i].transitionToState(WorldWind.FAILED);
}
this._state = newState;
};
// Intentionally not documented.
GestureRecognizer.prototype.canPreventRecognizer = function (that) {
return this != that && this.target == that.target && that.state == WorldWind.POSSIBLE &&
(this.requiredToFailByRecognizer(that) || !this.canRecognizeSimultaneouslyWith(that));
};
// Intentionally not documented.
GestureRecognizer.prototype.canBePreventedByRecognizer = function (that) {
return this != that && this.target == that.target && that.state == WorldWind.RECOGNIZED &&
(this.requiresRecognizerToFail(that) || !this.canRecognizeSimultaneouslyWith(that));
};
// Intentionally not documented.
GestureRecognizer.prototype.isWaitingForRecognizerToFail = function (that) {
return this != that && this.target == that.target && that.state == WorldWind.POSSIBLE &&
this.requiresRecognizerToFail(that);
};
/**
* Registers a gesture state listener on this GestureRecognizer. Registering state listeners using this function
* enables applications to receive notifications of gesture recognition.
*
* Listeners must implement a gestureStateChanged method to receive notifications. The gestureStateChanged method will
* receive one parameter containing a reference to the recognizer that changed state.
*
* @param listener The function to call when the event occurs.
* @throws {ArgumentError} If any argument is null or undefined.
*/
GestureRecognizer.prototype.addListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "addListener", "missingListener"));
}
this.listenerList.push(listener);
};
/**
* Removes a gesture state listener from this GestureRecognizer. The listener must be the same object passed to
* addListener. Calling removeListener with arguments that do not identify a currently registered
* listener has no effect.
*
* @param listener The listener to remove. Must be the same object passed to addListener.
* @throws {ArgumentError} If any argument is null or undefined.
*/
GestureRecognizer.prototype.removeListener = function (listener) {
if (!listener) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "removeListener", "missingListener"));
}
var index = this.listenerList.indexOf(listener);
if (index !== -1) {
this.listenerList.splice(index, 1); // remove the listener from the list
}
};
// Intentionally not documented.
GestureRecognizer.prototype.notifyListeners = function () {
for (var i = 0; i < this.listenerList.length; i++) {
this.listenerList[i].gestureStateChanged(this);
}
};
// Intentionally not documented.
GestureRecognizer.prototype.callGestureCallbacks = function () {
for (var i = 0, len = this._gestureCallbacks.length; i < len; i++) {
this._gestureCallbacks[i](this);
}
};
// Intentionally not documented.
GestureRecognizer.prototype.onGestureEvent = function (event) {
if (!this.enabled) {
return;
}
if (event.defaultPrevented && this.state === WorldWind.POSSIBLE) {
return; // ignore cancelled events while in the Possible state
}
var i, len;
try {
if (event.type === "mousedown") {
this.handleMouseDown(event);
} else if (event.type === "mousemove") {
this.handleMouseMove(event);
} else if (event.type === "mouseup") {
this.handleMouseUp(event);
} else if (event.type === "touchstart") {
for (i = 0, len = event.changedTouches.length; i < len; i++) {
this.handleTouchStart(event.changedTouches.item(i));
}
} else if (event.type === "touchmove") {
for (i = 0, len = event.changedTouches.length; i < len; i++) {
this.handleTouchMove(event.changedTouches.item(i));
}
} else if (event.type === "touchcancel") {
for (i = 0, len = event.changedTouches.length; i < len; i++) {
this.handleTouchCancel(event.changedTouches.item(i));
}
} else if (event.type === "touchend") {
for (i = 0, len = event.changedTouches.length; i < len; i++) {
this.handleTouchEnd(event.changedTouches.item(i));
}
} else if (event.type === "pointerdown" && event.pointerType === "mouse") {
this.handleMouseDown(event);
} else if (event.type === "pointermove" && event.pointerType === "mouse") {
this.handleMouseMove(event);
} else if (event.type === "pointercancel" && event.pointerType === "mouse") {
// Intentionally left blank. The W3C Pointer Events specification is ambiguous on what cancel means
// for mouse input, and there is no evidence that this event is actually generated (6/19/2015).
} else if (event.type === "pointerup" && event.pointerType === "mouse") {
this.handleMouseUp(event);
} else if (event.type === "pointerdown" && event.pointerType === "touch") {
this.handleTouchStart(event);
} else if (event.type === "pointermove" && event.pointerType === "touch") {
this.handleTouchMove(event);
} else if (event.type === "pointercancel" && event.pointerType === "touch") {
this.handleTouchCancel(event);
} else if (event.type === "pointerup" && event.pointerType === "touch") {
this.handleTouchEnd(event);
} else {
Logger.logMessage(Logger.LEVEL_INFO, "GestureRecognizer", "handleEvent",
"Unrecognized event type: " + event.type);
}
} catch (e) {
Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "handleEvent",
"Error handling event.\n" + e.toString());
}
};
// Intentionally not documented.
GestureRecognizer.prototype.handleMouseDown = function (event) {
if (event.type == "mousedown" && this._touches.length > 0) {
return; // ignore synthesized mouse down events on Android Chrome
}
var buttonBit = 1 << event.button;
if (buttonBit & this._mouseButtonMask != 0) {
return; // ignore redundant mouse down events
}
if (this._mouseButtonMask == 0) { // first button down
this._clientX = event.clientX;
this._clientY = event.clientY;
this._clientStartX = event.clientX;
this._clientStartY = event.clientY;
this._translationX = 0;
this._translationY = 0;
}
this._mouseButtonMask |= buttonBit;
this.mouseDown(event);
};
// Intentionally not documented.
GestureRecognizer.prototype.handleMouseMove = function (event) {
if (this._mouseButtonMask == 0) {
return; // ignore mouse move events when this recognizer does not consider any button to be down
}
if (this._clientX == event.clientX && this._clientY == event._clientY) {
return; // ignore redundant mouse move events
}
var dx = event.clientX - this._clientStartX,
dy = event.clientY - this._clientStartY,
w = this._translationWeight;
this._clientX = event.clientX;
this._clientY = event.clientY;
this._translationX = this._translationX * (1 - w) + dx * w;
this._translationY = this._translationY * (1 - w) + dy * w;
this.mouseMove(event);
};
// Intentionally not documented.
GestureRecognizer.prototype.handleMouseUp = function (event) {
var buttonBit = 1 << event.button;
if (buttonBit & this._mouseButtonMask == 0) {
return; // ignore mouse up events for buttons this recognizer does not consider to be down
}
this._mouseButtonMask &= ~buttonBit;
this.mouseUp(event);
if (this._mouseButtonMask == 0) {
this.resetIfEventsEnded(); // last button up
}
};
// Intentionally not documented.
GestureRecognizer.prototype.handleTouchStart = function (event) {
var touch = new Touch(event.identifier || event.pointerId, event.clientX, event.clientY); // touch events or pointer events
this._touches.push(touch);
if (this._touches.length == 1) { // first touch
this._clientX = event.clientX;
this._clientY = event.clientY;
this._clientStartX = event.clientX;
this._clientStartY = event.clientY;
this._translationX = 0;
this._translationY = 0;
this._touchCentroidShiftX = 0;
this._touchCentroidShiftY = 0;
} else {
this.touchesAddedOrRemoved();
}
this.touchStart(touch);
};
// Intentionally not documented.
GestureRecognizer.prototype.handleTouchMove = function (event) {
var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
if (index == -1) {
return; // ignore events for touches that did not start in this recognizer's target
}
var touch = this._touches[index];
if (touch.clientX == event.clientX && touch.clientY == event.clientY) {
return; // ignore redundant touch move events, which we've encountered on Android Chrome
}
touch.clientX = event.clientX;
touch.clientY = event.clientY;
var centroid = this.touchCentroid(),
dx = centroid.clientX - this._clientStartX + this._touchCentroidShiftX,
dy = centroid.clientY - this._clientStartY + this._touchCentroidShiftY,
w = this._translationWeight;
this._clientX = centroid.clientX;
this._clientY = centroid.clientY;
this._translationX = this._translationX * (1 - w) + dx * w;
this._translationY = this._translationY * (1 - w) + dy * w;
this.touchMove(touch);
};
// Intentionally not documented.
GestureRecognizer.prototype.handleTouchCancel = function (event) {
var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
if (index == -1) {
return; // ignore events for touches that did not start in this recognizer's target
}
var touch = this._touches[index];
this._touches.splice(index, 1);
this.touchesAddedOrRemoved();
this.touchCancel(touch);
this.resetIfEventsEnded();
};
// Intentionally not documented.
GestureRecognizer.prototype.handleTouchEnd = function (event) {
var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
if (index == -1) {
return; // ignore events for touches that did not start in this recognizer's target
}
var touch = this._touches[index];
this._touches.splice(index, 1);
this.touchesAddedOrRemoved();
this.touchEnd(touch);
this.resetIfEventsEnded();
};
// Intentionally not documented.
GestureRecognizer.prototype.resetIfEventsEnded = function () {
if (this._state != WorldWind.POSSIBLE && this._mouseButtonMask == 0 && this._touches.length == 0) {
this.reset();
}
};
// Intentionally not documented.
GestureRecognizer.prototype.touchesAddedOrRemoved = function () {
this._touchCentroidShiftX += this._clientX;
this._touchCentroidShiftY += this._clientY;
var centroid = this.touchCentroid();
this._clientX = centroid.clientX;
this._clientY = centroid.clientY;
this._touchCentroidShiftX -= this._clientX;
this._touchCentroidShiftY -= this._clientY;
};
// Intentionally not documented.
GestureRecognizer.prototype.touchCentroid = function () {
var x = 0,
y = 0;
for (var i = 0, len = this._touches.length; i < len; i++) {
var touch = this._touches[i];
x += touch.clientX / len;
y += touch.clientY / len;
}
return { clientX: x, clientY: y };
};
// Intentionally not documented.
GestureRecognizer.prototype.indexOfTouchWithId = function (identifier) {
for (var i = 0, len = this._touches.length; i < len; i++) {
if (this._touches[i].identifier == identifier) {
return i;
}
}
return -1;
};
export default GestureRecognizer;

View File

@ -0,0 +1,132 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports PanRecognizer
*/
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a pan gesture recognizer.
* @alias PanRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for touch panning gestures.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function PanRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
/**
*
* @type {Number}
*/
this.minNumberOfTouches = 1;
/**
*
* @type {Number}
*/
this.maxNumberOfTouches = Number.MAX_VALUE;
// Intentionally not documented.
this.interpretDistance = 20;
}
PanRecognizer.prototype = Object.create(GestureRecognizer.prototype);
// Documented in superclass.
PanRecognizer.prototype.mouseDown = function (event) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
}
};
// Documented in superclass.
PanRecognizer.prototype.touchMove = function (touch) {
if (this.state == WorldWind.POSSIBLE) {
if (this.shouldInterpret()) {
if (this.shouldRecognize()) {
this.state = WorldWind.BEGAN;
} else {
this.state = WorldWind.FAILED;
}
}
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.CHANGED;
}
};
// Documented in superclass.
PanRecognizer.prototype.touchEnd = function (touch) {
if (this.touchCount == 0) { // last touch ended
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.ENDED;
}
}
};
// Documented in superclass.
PanRecognizer.prototype.touchCancel = function (touch) {
if (this.touchCount == 0) { // last touch cancelled
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.CANCELLED;
}
}
};
// Documented in superclass.
PanRecognizer.prototype.prepareToRecognize = function () {
// set translation to zero when the pan begins
this.translationX = 0;
this.translationY = 0;
};
/**
*
* @returns {boolean}
* @protected
*/
PanRecognizer.prototype.shouldInterpret = function () {
var dx = this.translationX,
dy = this.translationY,
distance = Math.sqrt(dx * dx + dy * dy);
return distance > this.interpretDistance; // interpret touches when the touch centroid moves far enough
};
/**
*
* @returns {boolean}
* @protected
*/
PanRecognizer.prototype.shouldRecognize = function () {
var touchCount = this.touchCount;
return touchCount != 0
&& touchCount >= this.minNumberOfTouches
&& touchCount <= this.maxNumberOfTouches;
};
export default PanRecognizer;

View File

@ -0,0 +1,169 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports PinchRecognizer
*/
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a pinch gesture recognizer.
* @alias PinchRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for two finger pinch gestures.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function PinchRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
// Intentionally not documented.
this._scale = 1;
// Intentionally not documented.
this._offsetScale = 1;
// Intentionally not documented.
this.referenceDistance = 0;
// Intentionally not documented.
this.interpretThreshold = 20;
// Intentionally not documented.
this.weight = 0.4;
// Intentionally not documented.
this.pinchTouches = [];
}
PinchRecognizer.prototype = Object.create(GestureRecognizer.prototype);
Object.defineProperties(PinchRecognizer.prototype, {
scale: {
get: function () {
return this._scale * this._offsetScale;
}
}
});
// Documented in superclass.
PinchRecognizer.prototype.reset = function () {
GestureRecognizer.prototype.reset.call(this);
this._scale = 1;
this._offsetScale = 1;
this.referenceDistance = 0;
this.pinchTouches = [];
};
// Documented in superclass.
PinchRecognizer.prototype.mouseDown = function (event) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
}
};
// Documented in superclass.
PinchRecognizer.prototype.touchStart = function (touch) {
if (this.pinchTouches.length < 2) {
if (this.pinchTouches.push(touch) == 2) {
this.referenceDistance = this.currentPinchDistance();
this._offsetScale *= this._scale;
this._scale = 1;
}
}
};
// Documented in superclass.
PinchRecognizer.prototype.touchMove = function (touch) {
if (this.pinchTouches.length == 2) {
if (this.state == WorldWind.POSSIBLE) {
if (this.shouldRecognize()) {
this.state = WorldWind.BEGAN;
}
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
var distance = this.currentPinchDistance(),
newScale = Math.abs(distance / this.referenceDistance),
w = this.weight;
this._scale = this._scale * (1 - w) + newScale * w;
this.state = WorldWind.CHANGED;
}
}
};
// Documented in superclass.
PinchRecognizer.prototype.touchEnd = function (touch) {
var index = this.pinchTouches.indexOf(touch);
if (index != -1) {
this.pinchTouches.splice(index, 1);
}
// Transition to the ended state if this was the last touch.
if (this.touchCount == 0) { // last touch ended
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.ENDED;
}
}
};
// Documented in superclass.
PinchRecognizer.prototype.touchCancel = function (touch) {
var index = this.pinchTouches.indexOf(touch);
if (index != -1) {
this.pinchTouches.splice(index, 1);
}
// Transition to the cancelled state if this was the last touch.
if (this.touchCount == 0) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.CANCELLED;
}
}
};
// Documented in superclass.
PinchRecognizer.prototype.prepareToRecognize = function () {
this.referenceDistance = this.currentPinchDistance();
this._scale = 1;
};
// Intentionally not documented.
PinchRecognizer.prototype.shouldRecognize = function () {
var distance = this.currentPinchDistance();
return Math.abs(distance - this.referenceDistance) > this.interpretThreshold;
};
// Intentionally not documented.
PinchRecognizer.prototype.currentPinchDistance = function () {
var touch0 = this.pinchTouches[0],
touch1 = this.pinchTouches[1],
dx = touch0.clientX - touch1.clientX,
dy = touch0.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
};
export default PinchRecognizer;

View File

@ -0,0 +1,172 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports RotationRecognizer
*/
import Angle from '../geom/Angle';
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a rotation gesture recognizer.
* @alias RotationRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for two finger rotation gestures.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function RotationRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
// Intentionally not documented.
this._rotation = 0;
// Intentionally not documented.
this._offsetRotation = 0;
// Intentionally not documented.
this.referenceAngle = 0;
// Intentionally not documented.
this.interpretThreshold = 20;
// Intentionally not documented.
this.weight = 0.4;
// Intentionally not documented.
this.rotationTouches = [];
}
RotationRecognizer.prototype = Object.create(GestureRecognizer.prototype);
Object.defineProperties(RotationRecognizer.prototype, {
rotation: {
get: function () {
return this._rotation + this._offsetRotation;
}
}
});
// Documented in superclass.
RotationRecognizer.prototype.reset = function () {
GestureRecognizer.prototype.reset.call(this);
this._rotation = 0;
this._offsetRotation = 0;
this.referenceAngle = 0;
this.rotationTouches = [];
};
// Documented in superclass.
RotationRecognizer.prototype.mouseDown = function (event) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
}
};
// Documented in superclass.
RotationRecognizer.prototype.touchStart = function (touch) {
if (this.rotationTouches.length < 2) {
if (this.rotationTouches.push(touch) == 2) {
this.referenceAngle = this.currentTouchAngle();
this._offsetRotation += this._rotation;
this._rotation = 0;
}
}
};
// Documented in superclass.
RotationRecognizer.prototype.touchMove = function (touch) {
if (this.rotationTouches.length == 2) {
if (this.state == WorldWind.POSSIBLE) {
if (this.shouldRecognize()) {
this.state = WorldWind.BEGAN;
}
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
var angle = this.currentTouchAngle(),
newRotation = Angle.normalizedDegrees(angle - this.referenceAngle),
w = this.weight;
this._rotation = this._rotation * (1 - w) + newRotation * w;
this.state = WorldWind.CHANGED;
}
}
};
// Documented in superclass.
RotationRecognizer.prototype.touchEnd = function (touch) {
var index = this.rotationTouches.indexOf(touch);
if (index != -1) {
this.rotationTouches.splice(index, 1);
}
// Transition to the ended state if this was the last touch.
if (this.touchCount == 0) { // last touch ended
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.ENDED;
}
}
};
// Documented in superclass.
RotationRecognizer.prototype.touchCancel = function (touch) {
var index = this.rotationTouches.indexOf(touch);
if (index != -1) {
this.rotationTouches.splice(index, 1);
// Transition to the cancelled state if this was the last touch.
if (this.touchCount == 0) {
if (this.state == WorldWind.POSSIBLE) {
this.state = WorldWind.FAILED;
} else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
this.state = WorldWind.CANCELLED;
}
}
}
};
// Documented in superclass.
RotationRecognizer.prototype.prepareToRecognize = function () {
this.referenceAngle = this.currentTouchAngle();
this._rotation = 0;
};
// Intentionally not documented.
RotationRecognizer.prototype.shouldRecognize = function () {
var angle = this.currentTouchAngle(),
rotation = Angle.normalizedDegrees(angle - this.referenceAngle);
return Math.abs(rotation) > this.interpretThreshold;
};
// Intentionally not documented.
RotationRecognizer.prototype.currentTouchAngle = function () {
var touch0 = this.rotationTouches[0],
touch1 = this.rotationTouches[1],
dx = touch0.clientX - touch1.clientX,
dy = touch0.clientY - touch1.clientY;
return Math.atan2(dy, dx) * Angle.RADIANS_TO_DEGREES;
};
export default RotationRecognizer;

View File

@ -0,0 +1,182 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TapRecognizer
*/
import GestureRecognizer from '../gesture/GestureRecognizer';
/**
* Constructs a tap gesture recognizer.
* @alias TapRecognizer
* @constructor
* @augments GestureRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for single or multiple taps.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function TapRecognizer(target, callback) {
GestureRecognizer.call(this, target, callback);
/**
*
* @type {Number}
*/
this.numberOfTaps = 1;
/**
*
* @type {Number}
*/
this.numberOfTouches = 1;
// Intentionally not documented.
this.maxTouchMovement = 20;
// Intentionally not documented.
this.maxTapDuration = 500;
// Intentionally not documented.
this.maxTapInterval = 400;
// Intentionally not documented.
this.taps = [];
// Intentionally not documented.
this.timeout = null;
}
TapRecognizer.prototype = Object.create(GestureRecognizer.prototype);
// Documented in superclass.
TapRecognizer.prototype.reset = function () {
GestureRecognizer.prototype.reset.call(this);
this.taps = [];
this.cancelFailAfterDelay();
};
// Documented in superclass.
TapRecognizer.prototype.mouseDown = function (event) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
};
// Documented in superclass.
TapRecognizer.prototype.touchStart = function (touch) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
var tap;
if (this.touchCount > this.numberOfTouches) {
this.state = WorldWind.FAILED;
} else if (this.touchCount == 1) { // first touch started
tap = {
touchCount: this.touchCount,
clientX: this.clientX,
clientY: this.clientY
};
this.taps.push(tap);
this.failAfterDelay(this.maxTapDuration); // fail if the tap is down too long
} else {
tap = this.taps[this.taps.length - 1];
tap.touchCount = this.touchCount; // max number of simultaneous touches
tap.clientX = this.clientX; // touch centroid
tap.clientY = this.clientY;
}
};
// Documented in superclass.
TapRecognizer.prototype.touchMove = function (touch) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
var dx = this.translationX,
dy = this.translationY,
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > this.maxTouchMovement) {
this.state = WorldWind.FAILED;
}
};
// Documented in superclass.
TapRecognizer.prototype.touchEnd = function (touch) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
if (this.touchCount != 0) {
return; // wait until the last touch ends
}
var tapCount = this.taps.length,
tap = this.taps[tapCount - 1];
if (tap.touchCount != this.numberOfTouches) {
this.state = WorldWind.FAILED; // wrong number of touches
} else if (tapCount == this.numberOfTaps) {
this.clientX = this.taps[0].clientX;
this.clientY = this.taps[0].clientY;
this.state = WorldWind.RECOGNIZED;
} else {
this.failAfterDelay(this.maxTapInterval); // fail if the interval between taps is too long
}
};
// Documented in superclass.
TapRecognizer.prototype.touchCancel = function (touch) {
if (this.state != WorldWind.POSSIBLE) {
return;
}
this.state = WorldWind.FAILED;
};
// Intentionally not documented.
TapRecognizer.prototype.failAfterDelay = function (delay) {
var self = this;
if (self.timeout) {
window.clearTimeout(self.timeout);
}
self.timeout = window.setTimeout(function () {
self.timeout = null;
if (self.state == WorldWind.POSSIBLE) {
self.state = WorldWind.FAILED; // fail if we haven't already reached a terminal state
}
}, delay);
};
// Intentionally not documented.
TapRecognizer.prototype.cancelFailAfterDelay = function () {
var self = this;
if (self.timeout) {
window.clearTimeout(self.timeout);
self.timeout = null;
}
};
export default TapRecognizer;

View File

@ -0,0 +1,120 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TiltRecognizer
*/
import PanRecognizer from '../gesture/PanRecognizer';
/**
* Constructs a tilt gesture recognizer.
* @alias TiltRecognizer
* @constructor
* @augments PanRecognizer
* @classdesc A concrete gesture recognizer subclass that looks for two finger tilt gestures.
* @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
* @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
* function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
* e.g., <code>gestureCallback(recognizer)</code>.
* @throws {ArgumentError} If the specified target is null or undefined.
*/
function TiltRecognizer(target, callback) {
PanRecognizer.call(this, target, callback);
// Intentionally not documented.
this.maxTouchDistance = 250;
// Intentionally not documented.
this.maxTouchDivergence = 50;
}
// Intentionally not documented.
TiltRecognizer.LEFT = 1 << 0;
// Intentionally not documented.
TiltRecognizer.RIGHT = 1 << 1;
// Intentionally not documented.
TiltRecognizer.UP = 1 << 2;
// Intentionally not documented.
TiltRecognizer.DOWN = 1 << 3;
TiltRecognizer.prototype = Object.create(PanRecognizer.prototype);
// Documented in superclass.
TiltRecognizer.prototype.shouldInterpret = function () {
for (var i = 0, count = this.touchCount; i < count; i++) {
var touch = this.touch(i),
dx = touch.translationX,
dy = touch.translationY,
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > this.interpretDistance) {
return true; // interpret touches when any touch moves far enough
}
}
return false;
};
// Documented in superclass.
TiltRecognizer.prototype.shouldRecognize = function () {
var touchCount = this.touchCount;
if (touchCount < 2) {
return false;
}
var touch0 = this.touch(0),
touch1 = this.touch(1),
dx = touch0.clientX - touch1.clientX,
dy = touch0.clientY - touch1.clientY,
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > this.maxTouchDistance) {
return false; // touches must be close together
}
var tx = touch0.translationX - touch1.translationX,
ty = touch0.translationY - touch1.translationY,
divergence = Math.sqrt(tx * tx + ty * ty);
if (divergence > this.maxTouchDivergence) {
return false; // touches must be moving in a mostly parallel direction
}
var verticalMask = TiltRecognizer.UP | TiltRecognizer.DOWN,
dirMask0 = this.touchDirection(touch0) & verticalMask,
dirMask1 = this.touchDirection(touch1) & verticalMask;
return (dirMask0 & dirMask1) != 0; // touches must move in the same vertical direction
};
// Intentionally not documented.
TiltRecognizer.prototype.touchDirection = function (touch) {
var dx = touch.translationX,
dy = touch.translationY,
dirMask = 0;
if (Math.abs(dx) > Math.abs(dy)) {
dirMask |= dx < 0 ? TiltRecognizer.LEFT : 0;
dirMask |= dx > 0 ? TiltRecognizer.RIGHT : 0;
} else {
dirMask |= dy < 0 ? TiltRecognizer.UP : 0;
dirMask |= dy > 0 ? TiltRecognizer.DOWN : 0;
}
return dirMask;
};
export default TiltRecognizer;

View File

@ -0,0 +1,112 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Touch
*/
/**
* Constructs a touch point.
* @alias Touch
* @constructor
* @classdesc Represents a touch point.
* @param {Color} identifier A number uniquely identifying the touch point
* @param {Number} clientX The X coordinate of the touch point's location.
* @param {Number} clientY The Y coordinate of the touch point's location.
*/
function Touch(identifier, clientX, clientY) {
/**
* A number uniquely identifying this touch point.
* @type {Number}
* @readonly
*/
this.identifier = identifier;
// Intentionally not documented.
this._clientX = clientX;
// Intentionally not documented.
this._clientY = clientY;
// Intentionally not documented.
this._clientStartX = clientX;
// Intentionally not documented.
this._clientStartY = clientY;
}
Object.defineProperties(Touch.prototype, {
/**
* Indicates the X coordinate of this touch point's location.
* @type {Number}
* @memberof Touch.prototype
*/
clientX: {
get: function () {
return this._clientX;
},
set: function (value) {
this._clientX = value;
}
},
/**
* Indicates the Y coordinate of this touch point's location.
* @type {Number}
* @memberof Touch.prototype
*/
clientY: {
get: function () {
return this._clientY;
},
set: function (value) {
this._clientY = value;
}
},
/**
* Indicates this touch point's translation along the X axis since the touch started.
* @type {Number}
* @memberof Touch.prototype
*/
translationX: {
get: function () {
return this._clientX - this._clientStartX;
},
set: function (value) {
this._clientStartX = this._clientX - value;
}
},
/**
* Indicates this touch point's translation along the Y axis since the touch started.
* @type {Number}
* @memberof Touch.prototype
*/
translationY: {
get: function () {
return this._clientY - this._clientStartY;
},
set: function (value) {
this._clientStartY = this._clientY - value;
}
}
});
export default Touch;

View File

@ -0,0 +1,115 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ArcgisElevationCoverage
*/
import Sector from '../geom/Sector';
import TiledElevationCoverage from '../globe/TiledElevationCoverage';
import WmsUrlBuilder from '../util/WmsUrlBuilder';
import ElevationImage from './ElevationImage';
import Logger from '../util/Logger';
import WWMath from '../util/WWMath';
import ArcgisElevationWorker from 'worker!./ArcgisElevationWorker.js';
/**
* Constructs an Earth elevation coverage using Arcgis data.
* @alias ArcgisElevationCoverage
* @constructor
* @augments TiledElevationCoverage
* @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
*/
function ArcgisElevationCoverage() {
// see: http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer
TiledElevationCoverage.call(this, {
coverageSector: new Sector(-WWMath.MAX_LAT, WWMath.MAX_LAT, -180, 180),
resolution: 360 / 2 ** 16 / 256,
retrievalImageFormat: "application/bil16",
minElevation: -450,
maxElevation: 8700,
urlBuilder: new WmsUrlBuilder("http://localhost:2020/api/Map/Elev", "WorldElevation3D", "", "1.3.0")
});
this.displayName = "Arcgis Elevation Coverage";
this.worker = new ArcgisElevationWorker();
this.worker.onmessage = this.handleMessage.bind(this);
}
ArcgisElevationCoverage.prototype = Object.create(TiledElevationCoverage.prototype);
ArcgisElevationCoverage.prototype.retrieveTileImage = function (tile) {
if (this.currentRetrievals.indexOf(tile.tileKey) < 0) {
if (this.currentRetrievals.length > this.retrievalQueueSize) {
return;
}
this.worker.postMessage({
tileKey: tile.tileKey,
x: tile.column,
y: tile.row,
z: tile.level.levelNumber
});
this.currentRetrievals.push(tile.tileKey);
}
};
ArcgisElevationCoverage.prototype.handleMessage = function (evt) {
let { result, tileKey, url, data, msg } = evt.data;
this.removeFromCurrentRetrievals(tileKey);
let tile = this.tileCache.entryForKey(tileKey);
if (!tile) {
// tile has been released
return;
}
if (result === 'success') {
Logger.log(Logger.LEVEL_INFO, "Elevations retrieval succeeded: " + url);
this.loadElevationImage(tile, data);
this.absentResourceList.unmarkResourceAbsent(tileKey);
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
window.dispatchEvent(e);
} else if (result === 'fail') {
this.absentResourceList.markResourceAbsent(tileKey);
Logger.log(Logger.LEVEL_WARNING,
"Elevations retrieval failed (" + msg + "): " + url);
} else if (result === 'error') {
this.absentResourceList.markResourceAbsent(tileKey);
Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval failed: " + url);
} else if (result === 'timeout') {
this.absentResourceList.markResourceAbsent(tileKey);
Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval timed out: " + url);
}
};
// Intentionally not documented.
ArcgisElevationCoverage.prototype.loadElevationImage = function (tile, data) {
var elevationImage = new ElevationImage(tile.sector, data.width, data.height);
elevationImage.imageData = data.pixelData;
elevationImage.size = elevationImage.imageData.length * 4;
if (elevationImage.imageData) {
elevationImage.findMinAndMaxElevation();
this.imageCache.putEntry(tile.tileKey, elevationImage, elevationImage.size);
this.timestamp = Date.now();
}
};
export default ArcgisElevationCoverage;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports AsterV2ElevationCoverage
*/
import Sector from '../geom/Sector';
import TiledElevationCoverage from '../globe/TiledElevationCoverage';
import WmsUrlBuilder from '../util/WmsUrlBuilder';
/**
* Constructs an Earth elevation coverage using ASTER V2 data.
* @alias AsterV2ElevationCoverage
* @constructor
* @augments TiledElevationCoverage
* @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
*/
function AsterV2ElevationCoverage() {
TiledElevationCoverage.call(this, {
coverageSector: new Sector(-83.0001, 83.0001, -180, 180),
resolution: 0.000277777777778,
retrievalImageFormat: "application/bil16",
minElevation: -11000,
maxElevation: 8850,
urlBuilder: new WmsUrlBuilder("https://worldwind26.arc.nasa.gov/elev", "aster_v2", "", "1.3.0")
});
this.displayName = "ASTER V2 Earth Elevation Coverage";
}
AsterV2ElevationCoverage.prototype = Object.create(TiledElevationCoverage.prototype);
export default AsterV2ElevationCoverage;

View File

@ -0,0 +1,44 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports EarthElevationModel
*/
import AsterV2ElevationCoverage from '../globe/AsterV2ElevationCoverage';
import ElevationModel from '../globe/ElevationModel';
import GebcoElevationCoverage from '../globe/GebcoElevationCoverage';
import UsgsNedElevationCoverage from '../globe/UsgsNedElevationCoverage';
import UsgsNedHiElevationCoverage from '../globe/UsgsNedHiElevationCoverage';
import ArcgisElevationCoverage from './ArcgisElevationCoverage';
/**
* Constructs an EarthElevationModel consisting of three elevation coverages GEBCO, Aster V2, and USGS NED.
* @alias EarthElevationModel
* @constructor
*/
function EarthElevationModel() {
ElevationModel.call(this);
this.addCoverage(new ArcgisElevationCoverage());
// this.addCoverage(new GebcoElevationCoverage());
// this.addCoverage(new AsterV2ElevationCoverage());
// this.addCoverage(new UsgsNedElevationCoverage());
// this.addCoverage(new UsgsNedHiElevationCoverage());
}
EarthElevationModel.prototype = Object.create(ElevationModel.prototype);
export default EarthElevationModel;

View File

@ -0,0 +1,168 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ElevationCoverage
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Sector from '../geom/Sector';
/**
* Constructs an ElevationCoverage
* @alias ElevationCoverage
* @constructor
* @classdesc When used directly and not through a subclass, this class represents an elevation coverage
* whose elevations are zero at all locations.
* @param {Number} resolution The resolution of the coverage, in degrees. (To compute degrees from
* meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)
* @throws {ArgumentError} If the resolution argument is null, undefined, or zero.
*/
function ElevationCoverage(resolution) {
if (!resolution) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage", "constructor",
"missingResolution"));
}
/**
* Indicates the last time this coverage changed, in milliseconds since midnight Jan 1, 1970.
* @type {Number}
* @readonly
* @default Date.now() at construction
*/
this.timestamp = Date.now();
/**
* Indicates this coverage's display name.
* @type {String}
* @default "Coverage"
*/
this.displayName = "Coverage";
/**
* Indicates whether or not to use this coverage.
* @type {Boolean}
* @default true
*/
this._enabled = true;
/**
* The resolution of this coverage in degrees.
* @type {Number}
*/
this.resolution = resolution;
/**
* The sector this coverage spans.
* @type {Sector}
* @readonly
*/
this.coverageSector = Sector.FULL_SPHERE;
}
Object.defineProperties(ElevationCoverage.prototype, {
/**
* Indicates whether or not to use this coverage.
* @type {Boolean}
* @default true
*/
enabled: {
get: function () {
return this._enabled;
},
set: function (value) {
this._enabled = value;
this.timestamp = Date.now();
}
}
});
/**
* Returns the minimum and maximum elevations within a specified sector.
* @param {Sector} sector The sector for which to determine extreme elevations.
* @param {Number[]} result An array in which to return the requested minimum and maximum elevations.
* @returns {Boolean} true if the coverage completely fills the sector with data, false otherwise.
* @throws {ArgumentError} If any argument is null or undefined
*/
ElevationCoverage.prototype.minAndMaxElevationsForSector = function (sector, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage", "minAndMaxElevationsForSector", "missingSector"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage", "minAndMaxElevationsForSector", "missingResult"));
}
if (result[0] > 0) { // min elevation
result[0] = 0;
}
if (result[1] < 0) { // max elevation
result[1] = 0;
}
return true;
};
/**
* Returns the elevation at a specified location.
* @param {Number} latitude The location's latitude in degrees.
* @param {Number} longitude The location's longitude in degrees.
* @returns {Number} The elevation at the specified location, in meters. Returns null if the location is
* outside the coverage area of this coverage.
*/
ElevationCoverage.prototype.elevationAtLocation = function (latitude, longitude) {
return 0;
};
/**
* Returns the elevations at locations within a specified sector.
* @param {Sector} sector The sector for which to determine the elevations.
* @param {Number} numLat The number of latitudinal sample locations within the sector.
* @param {Number} numLon The number of longitudinal sample locations within the sector.
* @param {Number[]} result An array in which to return the requested elevations.
* @returns {Boolean} true if the result array was completely filled with elevation data, false otherwise.
* @throws {ArgumentError} If the specified sector or result array is null or undefined, or if either of the
* specified numLat or numLon values is less than one.
*/
ElevationCoverage.prototype.elevationsForGrid = function (sector, numLat, numLon, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage", "elevationsForGrid", "missingSector"));
}
if (numLat <= 0 || numLon <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage",
"elevationsForGrid", "numLat or numLon is less than 1"));
}
if (!result || result.length < numLat * numLon) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationCoverage",
"elevationsForGrid", "missingArray"));
}
for (var i = 0, len = result.length; i < len; i++) {
result[i] = 0;
}
return true;
};
export default ElevationCoverage;

View File

@ -0,0 +1,354 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ElevationImage
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import WWMath from '../util/WWMath';
/**
* Constructs an elevation image.
* @alias ElevationImage
* @constructor
* @classdesc Holds elevation values for an elevation tile.
* This class is typically not used directly by applications.
* @param {Sector} sector The sector spanned by this elevation image.
* @param {Number} imageWidth The number of longitudinal sample points in this elevation image.
* @param {Number} imageHeight The number of latitudinal sample points in this elevation image.
* @throws {ArgumentError} If the sector is null or undefined
*/
function ElevationImage(sector, imageWidth, imageHeight) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "constructor", "missingSector"));
}
/**
* The sector spanned by this elevation image.
* @type {Sector}
* @readonly
*/
this.sector = sector;
/**
* The number of longitudinal sample points in this elevation image.
* @type {Number}
* @readonly
*/
this.imageWidth = imageWidth;
/**
* The number of latitudinal sample points in this elevation image.
* @type {Number}
* @readonly
*/
this.imageHeight = imageHeight;
/**
* The size in bytes of this elevation image.
* @type {number}
* @readonly
*/
this.size = this.imageWidth * this.imageHeight;
/**
* Internal use only
* false if the entire image consists of NO_DATA values, true otherwise.
* @ignore
*/
this.hasData = true;
}
/**
* Internal use only
* The value that indicates a pixel contains no data.
* TODO: This will eventually need to become an instance property
* @ignore
*/
ElevationImage.NO_DATA = 0;
/**
* Internal use only
* Returns true if a set of elevation pixels represents the NO_DATA value.
* @ignore
*/
ElevationImage.isNoData = function (x0y0, x1y0, x0y1, x1y1) {
// TODO: Change this logic once proper NO_DATA value handling is in place.
var v = ElevationImage.NO_DATA;
return x0y0 === v &&
x1y0 === v &&
x0y1 === v &&
x1y1 === v;
};
/**
* Returns the pixel value at a specified coordinate in this elevation image. The coordinate origin is the
* image's lower left corner, so (0, 0) indicates the lower left pixel and (imageWidth-1, imageHeight-1)
* indicates the upper right pixel. This returns 0 if the coordinate indicates a pixel outside of this elevation
* image.
* @param x The pixel's X coordinate.
* @param y The pixel's Y coordinate.
* @returns {Number} The pixel value at the specified coordinate in this elevation image.
* Returns 0 if the coordinate indicates a pixel outside of this elevation image.
*/
ElevationImage.prototype.pixel = function (x, y) {
debugger;
if (x < 0 || x >= this.imageWidth) {
return 0;
}
if (y < 0 || y >= this.imageHeight) {
return 0;
}
y = this.imageHeight - y - 1; // flip the y coordinate origin to the lower left corner
return this.imageData[x + y * this.imageWidth];
};
/**
* Returns the elevation at a specified geographic location.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @returns {Number} The elevation at the specified location.
*/
ElevationImage.prototype.elevationAtLocation = function (latitude, longitude) {
var maxLat = this.sector.maxLatitude,
minLon = this.sector.minLongitude,
deltaLat = this.sector.deltaLatitude(),
deltaLon = this.sector.deltaLongitude(),
x = (this.imageWidth - 1) * (longitude - minLon) / deltaLon,
y = (this.imageHeight - 1) * (maxLat - latitude) / deltaLat,
x0 = Math.floor(WWMath.clamp(x, 0, this.imageWidth - 1)),
x1 = Math.floor(WWMath.clamp(x0 + 1, 0, this.imageWidth - 1)),
y0 = Math.floor(WWMath.clamp(y, 0, this.imageHeight - 1)),
y1 = Math.floor(WWMath.clamp(y0 + 1, 0, this.imageHeight - 1)),
pixels = this.imageData,
x0y0 = pixels[x0 + y0 * this.imageWidth],
x1y0 = pixels[x1 + y0 * this.imageWidth],
x0y1 = pixels[x0 + y1 * this.imageWidth],
x1y1 = pixels[x1 + y1 * this.imageWidth],
xf = x - x0,
yf = y - y0;
if (ElevationImage.isNoData(x0y0, x1y0, x0y1, x1y1)) {
return NaN;
}
return (1 - xf) * (1 - yf) * x0y0 +
xf * (1 - yf) * x1y0 +
(1 - xf) * yf * x0y1 +
xf * yf * x1y1;
};
/**
* Returns elevations for a specified sector.
* @param {Sector} sector The sector for which to return the elevations.
* @param {Number} numLat The number of sample points in the longitudinal direction.
* @param {Number} numLon The number of sample points in the latitudinal direction.
* @param {Number[]} result An array in which to return the computed elevations.
* @throws {ArgumentError} If either the specified sector or result argument is null or undefined, or if the
* specified number of sample points in either direction is less than 1.
*/
ElevationImage.prototype.elevationsForGrid = function (sector, numLat, numLon, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid", "missingSector"));
}
if (numLat < 1 || numLon < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid",
"The specified number of sample points is less than 1."));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid", "missingResult"));
}
var minLatSelf = this.sector.minLatitude,
maxLatSelf = this.sector.maxLatitude,
minLonSelf = this.sector.minLongitude,
maxLonSelf = this.sector.maxLongitude,
deltaLatSelf = maxLatSelf - minLatSelf,
deltaLonSelf = maxLonSelf - minLonSelf,
minLat = sector.minLatitude,
maxLat = sector.maxLatitude,
minLon = sector.minLongitude,
maxLon = sector.maxLongitude,
deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
lat, lon,
i, j, index = 0,
pixels = this.imageData;
for (j = 0, lat = minLat; j < numLat; j += 1, lat += deltaLat) {
if (j === numLat - 1) {
lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
}
if (lat >= minLatSelf && lat <= maxLatSelf) {
// Image y-coordinate of the specified location, given an image origin in the top-left corner.
var y = (this.imageHeight - 1) * (maxLatSelf - lat) / deltaLatSelf,
y0 = Math.floor(WWMath.clamp(y, 0, this.imageHeight - 1)),
y1 = Math.floor(WWMath.clamp(y0 + 1, 0, this.imageHeight - 1)),
yf = y - y0;
for (i = 0, lon = minLon; i < numLon; i += 1, lon += deltaLon) {
if (i === numLon - 1) {
lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
}
if (lon >= minLonSelf && lon <= maxLonSelf && isNaN(result[index])) {
// Image x-coordinate of the specified location, given an image origin in the top-left corner.
var x = (this.imageWidth - 1) * (lon - minLonSelf) / deltaLonSelf,
x0 = Math.floor(WWMath.clamp(x, 0, this.imageWidth - 1)),
x1 = Math.floor(WWMath.clamp(x0 + 1, 0, this.imageWidth - 1)),
xf = x - x0;
var x0y0 = pixels[x0 + y0 * this.imageWidth],
x1y0 = pixels[x1 + y0 * this.imageWidth],
x0y1 = pixels[x0 + y1 * this.imageWidth],
x1y1 = pixels[x1 + y1 * this.imageWidth];
if (ElevationImage.isNoData(x0y0, x1y0, x0y1, x1y1)) {
result[index] = NaN;
}
else {
result[index] = (1 - xf) * (1 - yf) * x0y0 +
xf * (1 - yf) * x1y0 +
(1 - xf) * yf * x0y1 +
xf * yf * x1y1;
}
}
index++;
}
} else {
index += numLon; // skip this row
}
}
};
/**
* Returns the minimum and maximum elevations within a specified sector.
* @param {Sector} sector The sector of interest. If null or undefined, the minimum and maximum elevations
* for the sector associated with this tile are returned.
* @returns {Number[]} An array containing the minimum and maximum elevations within the specified sector,
* or null if the specified sector does not include this elevation image's coverage sector or the image is filled with
* NO_DATA values.
*/
ElevationImage.prototype.minAndMaxElevationsForSector = function (sector) {
debugger;
if (!this.hasData) {
return null;
}
var result = [];
if (!sector) { // the sector is this sector
result[0] = this.minElevation;
result[1] = this.maxElevation;
} else if (sector.contains(this.sector)) { // The specified sector completely contains this image; return the image min and max.
if (result[0] > this.minElevation) {
result[0] = this.minElevation;
}
if (result[1] < this.maxElevation) {
result[1] = this.maxElevation;
}
} else { // The specified sector intersects a portion of this image; compute the min and max from intersecting pixels.
var maxLatSelf = this.sector.maxLatitude,
minLonSelf = this.sector.minLongitude,
deltaLatSelf = this.sector.deltaLatitude(),
deltaLonSelf = this.sector.deltaLongitude(),
minLatOther = sector.minLatitude,
maxLatOther = sector.maxLatitude,
minLonOther = sector.minLongitude,
maxLonOther = sector.maxLongitude;
// Image coordinates of the specified sector, given an image origin in the top-left corner. We take the floor and
// ceiling of the min and max coordinates, respectively, in order to capture all pixels that would contribute to
// elevations computed for the specified sector in a call to elevationsForSector.
var minY = Math.floor((this.imageHeight - 1) * (maxLatSelf - maxLatOther) / deltaLatSelf),
maxY = Math.ceil((this.imageHeight - 1) * (maxLatSelf - minLatOther) / deltaLatSelf),
minX = Math.floor((this.imageWidth - 1) * (minLonOther - minLonSelf) / deltaLonSelf),
maxX = Math.ceil((this.imageWidth - 1) * (maxLonOther - minLonSelf) / deltaLonSelf);
minY = WWMath.clamp(minY, 0, this.imageHeight - 1);
maxY = WWMath.clamp(maxY, 0, this.imageHeight - 1);
minX = WWMath.clamp(minX, 0, this.imageWidth - 1);
maxX = WWMath.clamp(maxX, 0, this.imageWidth - 1);
var pixels = this.imageData,
min = Number.MAX_VALUE,
max = -min;
for (var y = minY; y <= maxY; y++) {
for (var x = minX; x <= maxX; x++) {
var p = pixels[Math.floor(x + y * this.imageWidth)];
if (min > p) {
min = p;
}
if (max < p) {
max = p;
}
}
}
if (result[0] > min) {
result[0] = min;
}
if (result[1] < max) {
result[1] = max;
}
}
return result;
};
/**
* Determines the minimum and maximum elevations within this elevation image and stores those values within
* this object. See [minAndMaxElevationsForSector]{@link ElevationImage#minAndMaxElevationsForSector}
*/
ElevationImage.prototype.findMinAndMaxElevation = function () {
this.hasData = false;
if (this.imageData && this.imageData.length > 0) {
this.hasData = true;
this.minElevation = Number.MAX_VALUE;
this.maxElevation = -Number.MAX_VALUE;
var pixels = this.imageData,
pixelCount = this.imageWidth * this.imageHeight;
for (var i = 0; i < pixelCount; i++) {
var p = pixels[i];
if (this.minElevation > p) {
this.minElevation = p;
}
if (this.maxElevation < p) {
this.maxElevation = p;
}
}
}
};
export default ElevationImage;

View File

@ -0,0 +1,417 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ElevationModel
*/
import ArgumentError from '../error/ArgumentError';
import Location from '../geom/Location';
import Logger from '../util/Logger';
/**
* Constructs an elevation model.
* @alias ElevationModel
* @constructor
* @classdesc Represents the elevations for an area, often but not necessarily the whole globe.
*/
function ElevationModel() {
/**
* Internal use only
* The unique ID of this model.
* @type {Array}
* @ignore
*/
this.id = 0;
/**
* A string identifying this elevation model's current state. Used to compare states during rendering to
* determine whether globe-state dependent cached values must be updated. Applications typically do not
* interact with this property. It is primarily used by shapes and terrain generators.
* @memberof ElevationModel.prototype
* @readonly
* @type {String}
*/
this.stateKey = "";
/**
* The list of all elevation coverages usable by this model.
* @type {Array}
*/
this.coverages = [];
this.scratchLocation = new Location(0, 0);
this.computeStateKey();
}
Object.defineProperties(ElevationModel.prototype, {
/**
* Indicates the last time the coverages changed, in milliseconds since midnight Jan 1, 1970.
* @type {Number}
* @readonly
*/
timestamp: {
get: function () {
var maxTimestamp = 0;
var i, len;
for (i = 0, len = this.coverages.length; i < len; i++) {
var coverage = this.coverages[i];
if (maxTimestamp < coverage.timestamp) {
maxTimestamp = coverage.timestamp;
}
}
return maxTimestamp;
}
},
/**
* This model's minimum elevation in meters across all enabled coverages.
* @type {Number}
* @readonly
*/
minElevation: {
get: function () {
var minElevation = Number.MAX_VALUE;
for (var i = 0, len = this.coverages.length; i < len; i++) {
var coverage = this.coverages[i];
if (coverage.enabled && coverage.minElevation < minElevation) {
minElevation = coverage.minElevation;
}
}
return minElevation !== Number.MAX_VALUE ? minElevation : 0; // no coverages or all coverages disabled
}
},
/**
* This model's maximum elevation in meters across all enabled coverages.
* @type {Number}
* @readonly
*/
maxElevation: {
get: function () {
var maxElevation = -Number.MAX_VALUE;
for (var i = 0, len = this.coverages.length; i < len; i++) {
var coverage = this.coverages[i];
if (coverage.enabled && coverage.maxElevation > maxElevation) {
maxElevation = coverage.maxElevation;
}
}
return maxElevation !== -Number.MAX_VALUE ? maxElevation : 0; // no coverages or all coverages disabled
}
}
});
/**
* Internal use only
* Used to assign unique IDs to elevation models for use in their state key.
* @type {Number}
* @ignore
*/
ElevationModel.idPool = 0;
/**
* Internal use only
* Sets the state key to a new unique value.
* @ignore
*/
ElevationModel.prototype.computeStateKey = function () {
this.id = ++ElevationModel.idPool;
this.stateKey = "elevationModel " + this.id.toString() + " ";
};
/**
* Internal use only
* The comparison function used for sorting elevation coverages.
* @ignore
*/
ElevationModel.prototype.coverageComparator = function (coverage1, coverage2) {
var res1 = coverage1.resolution;
var res2 = coverage2.resolution;
// sort from lowest resolution to highest
return res1 > res2 ? -1 : res1 === res2 ? 0 : 1;
};
/**
* Internal use only
* Perform common actions required when the list of available coverages changes.
* @ignore
*/
ElevationModel.prototype.performCoverageListChangedActions = function () {
if (this.coverages.length > 1) {
this.coverages.sort(this.coverageComparator);
}
this.computeStateKey();
};
/**
* Adds an elevation coverage to this elevation model and sorts the list. Duplicate coverages will be ignored.
*
* @param coverage The elevation coverage to add.
* @return {Boolean} true if the ElevationCoverage as added; false if the coverage was a duplicate.
* @throws ArgumentError if the specified elevation coverage is null.
*/
ElevationModel.prototype.addCoverage = function (coverage) {
if (!coverage) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "addCoverage", "missingCoverage"));
}
if (!this.containsCoverage(coverage)) {
this.coverages.push(coverage);
this.performCoverageListChangedActions();
return true;
}
return false;
};
/**
* Removes all elevation coverages from this elevation model.
*/
ElevationModel.prototype.removeAllCoverages = function () {
if (this.coverages.length > 0) {
this.coverages = [];
this.performCoverageListChangedActions();
}
};
/**
* Removes a specific elevation coverage from this elevation model.
*
* @param coverage The elevation model to remove.
*
* @throws ArgumentError if the specified elevation coverage is null.
*/
ElevationModel.prototype.removeCoverage = function (coverage) {
if (!coverage) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "removeCoverage", "missingCoverage"));
}
var index = this.coverages.indexOf(coverage);
if (index >= 0) {
this.coverages.splice(index, 1);
this.performCoverageListChangedActions();
}
};
/**
* Returns true if this ElevationModel contains the specified ElevationCoverage, and false otherwise.
*
* @param coverage the ElevationCoverage to test.
* @return {Boolean} true if the ElevationCoverage is in this ElevationModel; false otherwise.
* @throws ArgumentError if the ElevationCoverage is null.
*/
ElevationModel.prototype.containsCoverage = function (coverage) {
if (!coverage) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "containsCoverage", "missingCoverage"));
}
var index = this.coverages.indexOf(coverage);
return index >= 0;
};
/**
* Returns the minimum and maximum elevations within a specified sector.
* @param {Sector} sector The sector for which to determine extreme elevations.
* @returns {Number[]} An array containing the minimum and maximum elevations within the specified sector. If no coverage
* can satisfy the request, a min and max of zero is returned.
* @throws {ArgumentError} If the specified sector is null or undefined.
*/
ElevationModel.prototype.minAndMaxElevationsForSector = function (sector) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "minAndMaxElevationsForSector", "missingSector"));
}
// Initialize the min and max elevations to the largest and smallest numbers, respectively. This has the
// effect of moving the extremes with each subsequent coverage as needed, without unintentionally capturing
// zero elevation. If we initialized this array with zeros the result would always contain zero, even when
// elevations in the sector are all above or below zero. This is critical for tile bounding boxes.
var result = [Number.MAX_VALUE, -Number.MAX_VALUE];
for (var i = this.coverages.length - 1; i >= 0; i--) {
var coverage = this.coverages[i];
if (coverage.enabled && coverage.coverageSector.intersects(sector)) {
if (coverage.minAndMaxElevationsForSector(sector, result)) {
break; // coverage completely fills the sector, ignore the remaining coverages
}
}
}
return result[0] !== Number.MAX_VALUE ? result : [0, 0]; // no coverages, all coverages disabled, or no coverages intersect the sector
};
/**
* Returns the elevation at a specified location.
* @param {Number} latitude The location's latitude in degrees.
* @param {Number} longitude The location's longitude in degrees.
* @returns {Number} The elevation at the specified location, in meters. Returns zero if the location is
* outside the coverage area of this model.
*/
ElevationModel.prototype.elevationAtLocation = function (latitude, longitude) {
var i, n = this.coverages.length;
for (i = n - 1; i >= 0; i--) {
var coverage = this.coverages[i];
if (coverage.enabled && coverage.coverageSector.containsLocation(latitude, longitude)) {
var elevation = coverage.elevationAtLocation(latitude, longitude);
if (elevation !== null) {
return elevation;
}
}
}
return 0;
};
/**
* Internal use only
* Returns the index of the coverage most closely matching the supplied resolution and overlapping the supplied
* sector or point area of interest. At least one area of interest parameter must be non-null.
* @param {Sector} sector An optional sector area of interest. Setting this parameter to null will cause it to be ignored.
* @param {Location} location An optional point area of interest. Setting this parameter to null will cause it to be ignored.
* @param {Number} targetResolution The desired elevation resolution, in degrees. (To compute degrees from
* meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)
* @returns {Number} The index of the coverage most closely matching the requested resolution.
* @ignore
*/
ElevationModel.prototype.preferredCoverageIndex = function (sector, location, targetResolution) {
var i,
n = this.coverages.length,
minResDiff = Number.MAX_VALUE,
minDiffIdx = -1;
for (i = 0; i < n; i++) {
var coverage = this.coverages[i],
validCoverage = coverage.enabled && (sector !== null && coverage.coverageSector.intersects(sector) ||
location !== null && coverage.coverageSector.containsLocation(location.latitude, location.longitude));
if (validCoverage) {
var resDiff = Math.abs(coverage.resolution - targetResolution);
if (resDiff > minResDiff) {
return minDiffIdx;
}
minResDiff = resDiff;
minDiffIdx = i;
}
}
return minDiffIdx;
};
/**
* Returns the best coverage available for a particular resolution,
* @param {Number} latitude The location's latitude in degrees.
* @param {Number} longitude The location's longitude in degrees.
* @param {Number} targetResolution The desired elevation resolution, in degrees. (To compute degrees from
* meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)
* @returns {ElevationCoverage} The coverage most closely matching the requested resolution. Returns null if no coverage is available at this
* location.
* @throws {ArgumentError} If the specified resolution is not positive.
*/
ElevationModel.prototype.bestCoverageAtLocation = function (latitude, longitude, targetResolution) {
if (!targetResolution || targetResolution < 0) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "bestCoverageAtLocation", "invalidResolution"));
}
this.scratchLocation.set(latitude, longitude);
var preferredIndex = this.preferredCoverageIndex(null, this.scratchLocation, targetResolution);
if (preferredIndex >= 0) {
return this.coverages[preferredIndex];
}
return null;
};
/**
* Returns the elevations at locations within a specified sector.
* @param {Sector} sector The sector for which to determine the elevations.
* @param {Number} numLat The number of latitudinal sample locations within the sector.
* @param {Number} numLon The number of longitudinal sample locations within the sector.
* @param {Number} targetResolution The desired elevation resolution, in degrees. (To compute degrees from
* meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)
* @param {Number[]} result An array in which to return the requested elevations.
* @returns {Number} The resolution actually achieved, which may be greater than that requested if the
* elevation data for the requested resolution is not currently available.
* @throws {ArgumentError} If the specified sector, targetResolution, or result array is null or undefined, or if either of the
* specified numLat or numLon values is less than one.
*/
ElevationModel.prototype.elevationsForGrid = function (sector, numLat, numLon, targetResolution, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForGrid", "missingSector"));
}
if (!numLat || !numLon || numLat < 1 || numLon < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForGrid",
"The specified number of latitudinal or longitudinal positions is less than one."));
}
if (!targetResolution) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForGrid", "missingTargetResolution"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForGrid", "missingResult"));
}
result.fill(NaN);
var resolution = Number.MAX_VALUE,
resultFilled = false,
preferredIndex = this.preferredCoverageIndex(sector, null, targetResolution);
if (preferredIndex >= 0) {
for (var i = preferredIndex; !resultFilled && i >= 0; i--) {
var coverage = this.coverages[i];
if (coverage.enabled && coverage.coverageSector.intersects(sector)) {
resultFilled = coverage.elevationsForGrid(sector, numLat, numLon, result);
if (resultFilled) {
resolution = coverage.resolution;
}
}
}
}
if (!resultFilled) {
var n = result.length;
for (i = 0; i < n; i++) {
if (isNaN(result[i])) {
result[i] = 0;
}
}
}
return resolution;
};
export default ElevationModel;

View File

@ -0,0 +1,47 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GebcoElevationCoverage
*/
import Sector from '../geom/Sector';
import TiledElevationCoverage from '../globe/TiledElevationCoverage';
import WmsUrlBuilder from '../util/WmsUrlBuilder';
/**
* Constructs an Earth elevation coverage using GEBCO data.
* @alias GebcoElevationCoverage
* @constructor
* @augments TiledElevationCoverage
* @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
*/
function GebcoElevationCoverage() {
TiledElevationCoverage.call(this, {
coverageSector: Sector.FULL_SPHERE,
resolution: 0.008333333333333,
retrievalImageFormat: "application/bil16",
minElevation: -11000,
maxElevation: 8850,
urlBuilder: new WmsUrlBuilder("https://worldwind26.arc.nasa.gov/elev", "GEBCO", "", "1.3.0")
});
this.displayName = "GEBCO Earth Elevation Coverage";
}
GebcoElevationCoverage.prototype = Object.create(TiledElevationCoverage.prototype);
export default GebcoElevationCoverage;

View File

@ -0,0 +1,651 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Globe
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import BoundingBox from '../geom/BoundingBox';
import Logger from '../util/Logger';
import ProjectionWgs84 from '../projections/ProjectionWgs84';
import Sector from '../geom/Sector';
import Tessellator from '../globe/Tessellator';
import Vec3 from '../geom/Vec3';
/**
* Constructs an ellipsoidal Globe with default radii for Earth (WGS84).
* @alias Globe
* @constructor
* @classdesc Represents an ellipsoidal globe. The default configuration represents Earth but may be changed.
* To configure for another planet, set the globe's equatorial and polar radii properties and its
* eccentricity-squared property.
* <p>
* A globe uses a Cartesian coordinate system whose origin is at the globe's center. It's Y axis points to the
* north pole, the Z axis points to the intersection of the prime meridian and the equator,
* and the X axis completes a right-handed coordinate system, is in the equatorial plane and 90 degrees east
* of the Z axis.
* <p>
* All Cartesian coordinates and elevations are in meters.
* @param {ElevationModel} elevationModel The elevation model to use for this globe.
* @param {GeographicProjection} projection The projection to apply to the globe. May be null or undefined,
* in which case no projection is applied and the globe is a WGS84 ellipsoid.
* @throws {ArgumentError} If the specified elevation model is null or undefined.
*/
function Globe(elevationModel, projection) {
if (!elevationModel) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
"constructor", "Elevation model is null or undefined."));
}
/**
* This globe's elevation model.
* @type {ElevationModel}
*/
this.elevationModel = elevationModel;
/**
* This globe's equatorial radius in meters.
*
* @type {Number}
* @default WGS 84 semi-major axis (6378137.0 meters)
*/
this.equatorialRadius = WorldWind.WGS84_SEMI_MAJOR_AXIS;
var f = 1 / WorldWind.WGS84_INVERSE_FLATTENING;
/**
* This globe's polar radius in meters.
* @type {Number}
* @default WGS 84 semi-minor axis (6356752.3142 meters). Taken from NGA.STND.0036_1.0.0_WGS84, section 3.2.
*/
this.polarRadius = this.equatorialRadius * (1 - f);
/**
* This globe's eccentricity squared.
* @type {Number}
* @default WGS 84 first eccentricity squared (6.694379990141e-3). Taken from NGA.STND.0036_1.0.0_WGS84, section 3.3.
*/
this.eccentricitySquared = 2 * f - f * f;
/**
* The tessellator used to create this globe's terrain.
* @type {Tessellator}
*/
this.tessellator = new Tessellator();
// Internal. Intentionally not documented.
this._projection = projection || new ProjectionWgs84();
// Internal. Intentionally not documented.
this._offset = 0;
// Internal. Intentionally not documented.
this.offsetVector = new Vec3(0, 0, 0);
// A unique ID for this globe. Intentionally not documented.
this.id = ++Globe.idPool;
this._stateKey = "globe " + this.id.toString() + " ";
}
Globe.idPool = 0; // Used to assign unique IDs to globes for use in their state keys.
Object.defineProperties(Globe.prototype, {
/**
* A string identifying this globe's current state. Used to compare states during rendering to
* determine whether globe-state dependent cached values must be updated. Applications typically do not
* interact with this property.
* @memberof Globe.prototype
* @readonly
* @type {String}
*/
stateKey: {
get: function () {
return this._stateKey + this.elevationModel.stateKey + "offset " + this.offset.toString() + " "
+ this.projection.stateKey;
}
},
/**
* Indicates whether this globe is 2D and continuous with itself -- that it should scroll continuously
* horizontally.
* @memberof Globe.prototype
* @readonly
* @type {Boolean}
*/
continuous: {
get: function () {
return this.projection.continuous;
}
},
/**
* The projection used by this globe.
* @memberof Globe.prototype
* @default {@link ProjectionWgs84}
* @type {GeographicProjection}
*/
projection: {
get: function () {
return this._projection;
},
set: function (projection) {
if (!projection) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
"projection", "missingProjection"));
}
if (this.projection != projection) {
this.tessellator = new Tessellator();
}
this._projection = projection;
}
},
/**
* The projection limits of the associated projection.
* @memberof Globe.prototype
* @type {Sector}
*/
projectionLimits: {
get: function () {
return this._projection.projectionLimits;
}
},
/**
* An offset to apply to this globe when translating between Geographic positions and Cartesian points.
* Used during scrolling to position points appropriately.
* Applications typically do not access this property. It is used by the associated globe.
* @memberof Globe.prototype
* @type {Number}
*/
offset: {
get: function () {
return this._offset;
},
set: function (offset) {
this._offset = offset;
this.offsetVector[0] = offset * 2 * Math.PI * this.equatorialRadius;
}
}
});
/**
* Indicates whether this is a 2D globe.
* @returns {Boolean} true if this is a 2D globe, otherwise false.
*/
Globe.prototype.is2D = function () {
return this.projection.is2D;
};
/**
* Computes a Cartesian point from a specified position.
* See this class' Overview section for a description of the Cartesian coordinate system used.
* @param {Number} latitude The position's latitude.
* @param {Number} longitude The position's longitude.
* @param {Number} altitude The position's altitude.
* @param {Vec3} result A reference to a pre-allocated {@link Vec3} in which to return the computed X,
* Y and Z Cartesian coordinates.
* @returns {Vec3} The result argument.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.computePointFromPosition = function (latitude, longitude, altitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointFromPosition",
"missingResult"));
}
return this.projection.geographicToCartesian(this, latitude, longitude, altitude, this.offsetVector, result);
};
/**
* Computes a Cartesian point from a specified location.
* See this class' Overview section for a description of the Cartesian coordinate system used.
* @param {Number} latitude The position's latitude.
* @param {Number} longitude The position's longitude.
* @param {Vec3} result A reference to a pre-allocated {@link Vec3} in which to return the computed X,
* Y and Z Cartesian coordinates.
* @returns {Vec3} The result argument.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.computePointFromLocation = function (latitude, longitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointFromLocation",
"missingResult"));
}
return this.computePointFromPosition(latitude, longitude, 0, result);
};
/**
* Computes a grid of Cartesian points within a specified sector and relative to a specified Cartesian
* reference point.
* <p>
* This method is used to compute a collection of points within a sector. It is used by tessellators to
* efficiently generate a tile's interior points. The number of points to generate is indicated by the numLon
* and numLat parameters.
* <p>
* For each implied position within the sector, an elevation value is specified via an array of elevations. The
* calculation at each position incorporates the associated elevation. There must be numLat x numLon elevations
* in the array.
*
* @param {Sector} sector The sector for which to compute the points.
* @param {Number} numLat The number of latitudinal points in the grid.
* @param {Number} numLon The number of longitudinal points in the grid.
* @param {Number[]} elevations An array of elevations to incorporate in the point calculations. There must be
* one elevation value in the array for each generated point. Elevations are in meters. There must be
* numLat x numLon elevations in the array.
* @param {Vec3} referencePoint The X, Y and Z Cartesian coordinates to subtract from the computed coordinates.
* This makes the computed coordinates relative to the specified point.
* @param {Float32Array} result A typed array to hold the computed coordinates. It must be at least of
* size numLat x numLon. The points are returned in row major order, beginning with the row of minimum latitude.
* @returns {Float32Array} The specified result argument.
* @throws {ArgumentError} if the specified sector, elevations array or results arrays are null or undefined, or
* if the lengths of any of the arrays are insufficient.
*/
Globe.prototype.computePointsForGrid = function (sector, numLat, numLon, elevations, referencePoint, result) {
if (!sector) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
"computePointsFromPositions", "missingSector"));
}
if (numLat < 1 || numLon < 1) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
"Number of latitude or longitude locations is less than one."));
}
var numPoints = numLat * numLon;
if (!elevations || elevations.length < numPoints) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
"Elevations array is null, undefined or insufficient length."));
}
if (!result || result.length < numPoints) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
"Result array is null, undefined or insufficient length."));
}
return this.projection.geographicToCartesianGrid(this, sector, numLat, numLon, elevations, referencePoint,
this.offsetVector, result);
};
/**
* Computes a geographic position from a specified Cartesian point.
*
* See this class' Overview section for a description of the Cartesian coordinate system used.
*
* @param {Number} x The X coordinate.
* @param {Number} y The Y coordinate.
* @param {Number} z The Z coordinate.
* @param {Position} result A pre-allocated {@link Position} instance in which to return the computed position.
* @returns {Position} The specified result position.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.computePositionFromPoint = function (x, y, z, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePositionFromPoint",
"missingResult"));
}
this.projection.cartesianToGeographic(this, x, y, z, this.offsetVector, result);
// Wrap if the globe is continuous.
if (this.continuous) {
if (result.longitude < -180) {
result.longitude += 360;
} else if (result.longitude > 180) {
result.longitude -= 360;
}
}
return result;
};
/**
* Computes the radius of this globe at a specified location.
* @param {Number} latitude The locations' latitude.
* @param {Number} longitude The locations' longitude.
* @returns {Number} The radius at the specified location.
*/
Globe.prototype.radiusAt = function (latitude, longitude) {
var sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
rpm = this.equatorialRadius / Math.sqrt(1.0 - this.eccentricitySquared * sinLat * sinLat);
return rpm * Math.sqrt(1.0 + (this.eccentricitySquared * this.eccentricitySquared - 2.0 * this.eccentricitySquared) * sinLat * sinLat);
};
/**
* Computes the normal vector to this globe's surface at a specified location.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
* normal vector is unit length.
* @returns {Vec3} The specified result vector. The returned normal vector is unit length.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.surfaceNormalAtLocation = function (latitude, longitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "surfaceNormalAtLocation",
"missingResult"));
}
// For backwards compatibility, check whether the projection defines a surfaceNormalAtLocation function
// before calling it. If it's not available, use the old code to compute the normal.
if (this.projection.surfaceNormalAtLocation) {
return this.projection.surfaceNormalAtLocation(this, latitude, longitude, result);
}
if (this.is2D()) {
result[0] = 0;
result[1] = 0;
result[2] = 1;
return result;
}
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
result[0] = cosLat * sinLon;
result[1] = sinLat;
result[2] = cosLat * cosLon;
return result.normalize();
};
/**
* Computes the normal vector to this globe's surface at a specified Cartesian point.
* @param {Number} x The point's X coordinate.
* @param {Number} y The point's Y coordinate.
* @param {Number} z The point's Z coordinate.
* @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
* normal vector is unit length.
* @returns {Vec3} The specified result vector.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.surfaceNormalAtPoint = function (x, y, z, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "surfaceNormalAtPoint",
"missingResult"));
}
// For backwards compatibility, check whether the projection defines a surfaceNormalAtPoint function
// before calling it. If it's not available, use the old code to compute the normal.
if (this.projection.surfaceNormalAtPoint) {
return this.projection.surfaceNormalAtPoint(this, x, y, z, result);
}
if (this.is2D()) {
result[0] = 0;
result[1] = 0;
result[2] = 1;
return result;
}
var eSquared = this.equatorialRadius * this.equatorialRadius,
polSquared = this.polarRadius * this.polarRadius;
result[0] = x / eSquared;
result[1] = y / polSquared;
result[2] = z / eSquared;
return result.normalize();
};
/**
* Computes the north-pointing tangent vector to this globe's surface at a specified location.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
* tangent vector is unit length.
* @returns {Vec3} The specified result vector.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.northTangentAtLocation = function (latitude, longitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "northTangentAtLocation",
"missingResult"));
}
return this.projection.northTangentAtLocation(this, latitude, longitude, result);
};
/**
* Computes the north-pointing tangent vector to this globe's surface at a specified Cartesian point.
* @param {Number} x The point's X coordinate.
* @param {Number} y The point's Y coordinate.
* @param {Number} z The point's Z coordinate.
* @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
* tangent vector is unit length.
* @returns {Vec3} The specified result vector.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Globe.prototype.northTangentAtPoint = function (x, y, z, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "northTangentAtPoint",
"missingResult"));
}
return this.projection.northTangentAtPoint(this, x, y, z, this.offsetVector, result);
};
/**
* Indicates whether this globe intersects a specified frustum.
* @param {Frustum} frustum The frustum to test.
* @returns {Boolean} true if this globe intersects the frustum, otherwise false.
* @throws {ArgumentError} If the specified frustum is null or undefined.
*/
Globe.prototype.intersectsFrustum = function (frustum) {
if (!frustum) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsFrustum", "missingFrustum"));
}
if (this.is2D()) {
var bbox = new BoundingBox();
bbox.setToSector(Sector.FULL_SPHERE, this, this.elevationModel.minElevation,
this.elevationModel.maxElevation);
return bbox.intersectsFrustum(frustum);
}
if (frustum.far.distance <= this.equatorialRadius)
return false;
if (frustum.left.distance <= this.equatorialRadius)
return false;
if (frustum.right.distance <= this.equatorialRadius)
return false;
if (frustum.top.distance <= this.equatorialRadius)
return false;
if (frustum.bottom.distance <= this.equatorialRadius)
return false;
if (frustum.near.distance <= this.equatorialRadius)
return false;
return true;
};
/**
* Computes the first intersection of this globe with a specified line. The line is interpreted as a ray;
* intersection points behind the line's origin are ignored.
* @param {Line} line The line to intersect with this globe.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
* @returns {boolean} true If the ray intersects the globe, otherwise false.
* @throws {ArgumentError} If the specified line or result argument is null or undefined.
*/
Globe.prototype.intersectsLine = function (line, result) {
if (!line) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectWithRay", "missingLine"));
}
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsLine", "missingResult"));
}
// Taken from "Mathematics for 3D Game Programming and Computer Graphics, Third Edition", Section 6.2.3.
//
// Note that the parameter n from equations 6.70 and 6.71 is omitted here. For an ellipsoidal globe this
// parameter is always 1, so its square and its product with any other value simplifies to the identity.
var vx = line.direction[0],
vy = line.direction[1],
vz = line.direction[2],
sx = line.origin[0],
sy = line.origin[1],
sz = line.origin[2],
t;
if (this.is2D()) {
if (vz == 0 && sz != 0) { // ray is parallel to and not coincident with the XY plane
return false;
}
t = -sz / vz; // intersection distance, simplified for the XY plane
if (t < 0) { // intersection is behind the ray's origin
return false;
}
result[0] = sx + vx * t;
result[1] = sy + vy * t;
result[2] = sz + vz * t;
return true;
} else {
var eqr = this.equatorialRadius, eqr2 = eqr * eqr, m = eqr / this.polarRadius, m2 = m * m, a, b, c, d;
a = vx * vx + m2 * vy * vy + vz * vz;
b = 2 * (sx * vx + m2 * sy * vy + sz * vz);
c = sx * sx + m2 * sy * sy + sz * sz - eqr2;
d = b * b - 4 * a * c; // discriminant
if (d < 0) {
return false;
}
t = (-b - Math.sqrt(d)) / (2 * a);
// check if the nearest intersection point is in front of the origin of the ray
if (t > 0) {
result[0] = sx + vx * t;
result[1] = sy + vy * t;
result[2] = sz + vz * t;
return true;
}
t = (-b + Math.sqrt(d)) / (2 * a);
// check if the second intersection point is in the front of the origin of the ray
if (t > 0) {
result[0] = sx + vx * t;
result[1] = sy + vy * t;
result[2] = sz + vz * t;
return true;
}
// the intersection points were behind the origin of the provided line
return false;
}
};
/**
* Returns the time at which any elevations associated with this globe last changed.
* @returns {Number} The time in milliseconds relative to the Epoch of the most recent elevation change.
*/
Globe.prototype.elevationTimestamp = function () {
return this.elevationModel.timestamp;
};
/**
* Returns this globe's minimum elevation.
* @returns {Number} This globe's minimum elevation.
*/
Globe.prototype.minElevation = function () {
return this.elevationModel.minElevation;
};
/**
* Returns this globe's maximum elevation.
* @returns {Number} This globe's maximum elevation.
*/
Globe.prototype.maxElevation = function () {
return this.elevationModel.maxElevation;
};
/**
* Returns the minimum and maximum elevations within a specified sector of this globe.
* @param {Sector} sector The sector for which to determine extreme elevations.
* @returns {Number[]} The An array containing the minimum and maximum elevations.
* @throws {ArgumentError} If the specified sector is null or undefined.
*/
Globe.prototype.minAndMaxElevationsForSector = function (sector) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "minAndMaxElevationsForSector",
"missingSector"));
}
return this.elevationModel.minAndMaxElevationsForSector(sector);
};
/**
* Returns the elevation at a specified location.
* @param {Number} latitude The location's latitude in degrees.
* @param {Number} longitude The location's longitude in degrees.
* @returns {Number} The elevation at the specified location, in meters. Returns zero if the location is
* outside the coverage area of this elevation model.
*/
Globe.prototype.elevationAtLocation = function (latitude, longitude) {
return this.elevationModel.elevationAtLocation(latitude, longitude);
};
/**
* Returns the elevations at locations within a specified sector.
* @param {Sector} sector The sector for which to determine the elevations.
* @param {Number} numLat The number of latitudinal sample locations within the sector.
* @param {Number} numLon The number of longitudinal sample locations within the sector.
* @param {Number} targetResolution The desired elevation resolution, in degrees. (To compute degrees from
* meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)
* @param {Number[]} result An array in which to return the requested elevations.
* @returns {Number} The resolution actually achieved, which may be greater than that requested if the
* elevation data for the requested resolution is not currently available.
* @throws {ArgumentError} If the specified sector or result array is null or undefined, or if either of the
* specified numLat or numLon values is less than one.
*/
Globe.prototype.elevationsForGrid = function (sector, numLat, numLon, targetResolution, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "elevationsForSector", "missingSector"));
}
if (numLat <= 0 || numLon <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
"elevationsForSector", "numLat or numLon is less than 1"));
}
if (!result || result.length < numLat * numLon) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
"elevationsForSector", "missingArray"));
}
return this.elevationModel.elevationsForGrid(sector, numLat, numLon, targetResolution, result);
};
export default Globe;

View File

@ -0,0 +1,226 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Terrain
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Vec3 from '../geom/Vec3';
/**
* Constructs a Terrain object.
* @alias Terrain
* @constructor
* @classdesc Represents terrain and provides functions for computing points on or relative to the terrain.
* Applications do not typically interact directly with this class.
*/
function Terrain(globe, tessellator, terrainTiles, verticalExaggeration) {
/**
* The globe associated with this terrain.
* @type {Globe}
*/
this.globe = globe;
/**
* The vertical exaggeration of this terrain.
* @type {Number}
*/
this.verticalExaggeration = verticalExaggeration;
/**
* The sector spanned by this terrain.
* @type {Sector}
*/
this.sector = terrainTiles.sector;
/**
* The tessellator used to generate this terrain.
* @type {Tessellator}
*/
this.tessellator = tessellator;
/**
* The surface geometry for this terrain
* @type {TerrainTile[]}
*/
this.surfaceGeometry = terrainTiles.tileArray;
/**
* A string identifying this terrain's current state. Used to compare states during rendering to
* determine whether state dependent cached values must be updated. Applications typically do not
* interact with this property.
* @readonly
* @type {String}
*/
this.stateKey = globe.stateKey + " ve " + verticalExaggeration.toString();
}
Terrain.scratchPoint = new Vec3(0, 0, 0);
/**
* Computes a Cartesian point at a location on the surface of this terrain.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @param {Number} offset Distance above the terrain, in meters, at which to compute the point.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
* @returns {Vec3} The specified result parameter, set to the coordinates of the computed point. If the
* specfied location is not within this terrain, the associated globe is used to compute the point.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Terrain.prototype.surfacePoint = function (latitude, longitude, offset, result) {
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "surfacePoint", "missingResult"));
}
for (var i = 0, len = this.surfaceGeometry.length; i < len; i++) {
if (this.surfaceGeometry[i].sector.containsLocation(latitude, longitude)) {
this.surfaceGeometry[i].surfacePoint(latitude, longitude, result);
if (offset) {
var normal = this.globe.surfaceNormalAtPoint(result[0], result[1], result[2], Terrain.scratchPoint);
result[0] += normal[0] * offset;
result[1] += normal[1] * offset;
result[2] += normal[2] * offset;
}
return result;
}
}
// No tile was found that contains the location, so approximate one using the globe.
var h = offset + this.globe.elevationAtLocation(latitude, longitude) * this.verticalExaggeration;
this.globe.computePointFromPosition(latitude, longitude, h, result);
return result;
};
/**
* Computes a Cartesian point at a location on the surface of this terrain according to a specified
* altitude mode.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @param {Number} offset Distance above the terrain, in meters relative to the specified altitude mode, at
* which to compute the point.
* @param {String} altitudeMode The altitude mode to use to compute the point. Recognized values are
* WorldWind.ABSOLUTE, WorldWind.CLAMP_TO_GROUND and
* WorldWind.RELATIVE_TO_GROUND. The mode WorldWind.ABSOLUTE is used if the
* specified mode is null, undefined or unrecognized, or if the specified location is outside this terrain.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
* @returns {Vec3} The specified result parameter, set to the coordinates of the computed point.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
Terrain.prototype.surfacePointForMode = function (latitude, longitude, offset, altitudeMode, result) {
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "surfacePointForMode", "missingResult"));
}
if (!altitudeMode)
altitudeMode = WorldWind.ABSOLUTE;
if (altitudeMode === WorldWind.CLAMP_TO_GROUND) {
return this.surfacePoint(latitude, longitude, 0, result);
} else if (altitudeMode === WorldWind.RELATIVE_TO_GROUND) {
return this.surfacePoint(latitude, longitude, offset, result);
} else {
var height = offset * this.verticalExaggeration;
this.globe.computePointFromPosition(latitude, longitude, height, result);
return result;
}
};
/**
* Initializes rendering state to draw a succession of terrain tiles.
* @param {DrawContext} dc The current draw context.
*/
Terrain.prototype.beginRendering = function (dc) {
if (this.globe && this.globe.tessellator) {
this.globe.tessellator.beginRendering(dc);
}
};
/**
* Restores rendering state after drawing a succession of terrain tiles.
* @param {DrawContext} dc The current draw context.
*/
Terrain.prototype.endRendering = function (dc) {
if (this.globe && this.globe.tessellator) {
this.globe.tessellator.endRendering(dc);
}
};
/**
* Initializes rendering state for drawing a specified terrain tile.
* @param {DrawContext} dc The current draw context.
* @param {TerrainTile} terrainTile The terrain tile subsequently drawn via this tessellator's render function.
* @throws {ArgumentError} If the specified tile is null or undefined.
*/
Terrain.prototype.beginRenderingTile = function (dc, terrainTile) {
if (!terrainTile) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "beginRenderingTile", "missingTile"));
}
if (this.globe && this.globe.tessellator) {
this.globe.tessellator.beginRenderingTile(dc, terrainTile);
}
};
/**
* Restores rendering state after drawing the most recent tile specified to
* [beginRenderingTile]{@link Terrain#beginRenderingTile}.
* @param {DrawContext} dc The current draw context.
* @param {TerrainTile} terrainTile The terrain tile most recently rendered.
* @throws {ArgumentError} If the specified tile is null or undefined.
*/
Terrain.prototype.endRenderingTile = function (dc, terrainTile) {
// Intentionally empty.
};
/**
* Renders a specified terrain tile.
* @param {DrawContext} dc The current draw context.
* @param {TerrainTile} terrainTile The terrain tile to render.
* @throws {ArgumentError} If the specified tile is null or undefined.
*/
Terrain.prototype.renderTile = function (dc, terrainTile) {
if (!terrainTile) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "renderTile", "missingTile"));
}
if (this.globe && this.globe.tessellator) {
this.globe.tessellator.renderTile(dc, terrainTile);
}
};
/**
* Causes this terrain to perform the picking operations appropriate for the draw context's pick settings.
* Normally, this draws the terrain in a unique pick color and computes the picked terrain position. When the
* draw context is set to region picking mode this omits the computation of a picked terrain position.
* @param {DrawContext} dc The current draw context.
*/
Terrain.prototype.pick = function (dc) {
if (this.globe && this.globe.tessellator) {
this.globe.tessellator.pick(dc, this.surfaceGeometry, this); // use this terrain as the userObject
}
};
export default Terrain;

View File

@ -0,0 +1,230 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TerrainTile
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import Tile from '../util/Tile';
/**
* Constructs a terrain tile.
* @alias TerrainTile
* @constructor
* @augments Tile
* @classdesc Represents a portion of a globe's terrain. Applications typically do not interact directly with
* this class.
* @param {Sector} sector The sector this tile covers.
* @param {Level} level The level this tile is associated with.
* @param {Number} row This tile's row in the associated level.
* @param {Number} column This tile's column in the associated level.
* @throws {ArgumentError} If the specified sector or level is null or undefined or the row or column arguments
* are less than zero.
*/
function TerrainTile(sector, level, row, column) {
Tile.call(this, sector, level, row, column); // args are checked in the superclass' constructor
/**
* The transformation matrix that maps tile local coordinates to model coordinates.
* @type {Matrix}
*/
this.transformationMatrix = Matrix.fromIdentity();
/**
* The tile's model coordinate points.
* @type {Float32Array}
*/
this.points = null;
/**
* Indicates the state of this tile when the model coordinate points were last updated. This is used to
* invalidate the points when this tile's state changes.
* @type {String}
*/
this.pointsStateKey = null;
/**
* Indicates the state of this tile when the model coordinate VBO was last uploaded to GL. This is used to
* invalidate the VBO when the tile's state changes.
* @type {String}
*/
this.pointsVboStateKey = null;
// Internal use. Intentionally not documented.
this.neighborMap = {};
this.neighborMap[WorldWind.NORTH] = null;
this.neighborMap[WorldWind.SOUTH] = null;
this.neighborMap[WorldWind.EAST] = null;
this.neighborMap[WorldWind.WEST] = null;
// Internal use. Intentionally not documented.
this._stateKey = null;
// Internal use. Intentionally not documented.
this._elevationTimestamp = null;
// Internal use. Intentionally not documented.
this.scratchArray = [];
}
TerrainTile.prototype = Object.create(Tile.prototype);
Object.defineProperties(TerrainTile.prototype, {
/**
* A string identifying the state of this tile as a function of the elevation model's timestamp and this
* tile's neighbors. Used to compare states during rendering to determine whether cached values must be
* updated. Applications typically do not interact with this property.
* @type {String}
* @memberof TerrainTile.prototype
* @readonly
*/
stateKey: {
get: function () {
if (!this._stateKey) {
this._stateKey = this.computeStateKey();
}
return this._stateKey;
}
}
});
/**
* Indicates the level of the tile adjacent to this tile in a specified direction. This returns null when this
* tile has no neighbor in that direction.
* @param {String} direction The cardinal direction. Must be one of WorldWind.NORTH, WorldWind.SOUTH,
* WorldWind.EAST or WorldWind.WEST.
* @returns {Level} The neighbor tile's level in the specified direction, or null if there is no neighbor.
*/
TerrainTile.prototype.neighborLevel = function (direction) {
return this.neighborMap[direction];
};
/**
* Specifies the level of the tile adjacent to this tile in a specified direction.
* @param {String} direction The cardinal direction. Must be one of WorldWind.NORTH, WorldWind.SOUTH,
* WorldWind.EAST or WorldWind.WEST.
* @param {Level} level The neighbor tile's level in the specified direction, or null to indicate that there is
* no neighbor in that direction.
*/
TerrainTile.prototype.setNeighborLevel = function (direction, level) {
this.neighborMap[direction] = level;
this._stateKey = null; // cause updates to any neighbor-dependent cached state
};
/**
* Computes a point on the terrain at a specified location.
* @param {Number} latitude The location's latitude.
* @param {Number} longitude The location's longitude.
* @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
* @returns {Vec3} The result argument set to the computed point.
* @throws {ArgumentError} If the specified result argument is null or undefined.
*/
TerrainTile.prototype.surfacePoint = function (latitude, longitude, result) {
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTile", "surfacePoint", "missingResult"));
}
var tileSector = this.sector,
minLat = tileSector.minLatitude,
maxLat = tileSector.maxLatitude,
minLon = tileSector.minLongitude,
maxLon = tileSector.maxLongitude,
tileWidth = this.tileWidth,
tileHeight = this.tileHeight,
s, t, si, ti, rowStride, vertices, points, k, sf, tf, x, y, z;
// Compute the location's horizontal (s) and vertical (t) parameterized coordinates within the tiles 2D grid of
// points as a floating-point value in the range [0, tileWidth] and [0, tileHeight]. These coordinates indicate
// which cell contains the location, as well as the location's placement within the cell. Note that this method
// assumes that the caller has tested whether the location is contained within the tile's sector.
s = (longitude - minLon) / (maxLon - minLon) * tileWidth;
t = (latitude - minLat) / (maxLat - minLat) * tileHeight;
// Get the coordinates for the four vertices defining the cell this point is in. Tile vertices start in the lower
// left corner and proceed in row major order across the tile. The tile contains one more vertex per row or
// column than the tile width or height. Vertices in the points array are organized in the
// following order: lower-left, lower-right, upper-left, upper-right. The cell's diagonal starts at the
// lower-left vertex and ends at the upper-right vertex.
si = s < tileWidth ? Math.floor(s) : tileWidth - 1;
ti = t < tileHeight ? Math.floor(t) : tileHeight - 1;
rowStride = tileWidth + 1;
vertices = this.points;
points = this.scratchArray; // temporary working buffer
k = 3 * (si + ti * rowStride); // lower-left and lower-right vertices
for (var i = 0; i < 6; i++) {
points[i] = vertices[k + i];
}
k = 3 * (si + (ti + 1) * rowStride); // upper-left and upper-right vertices
for (var j = 6; j < 12; j++) {
points[j] = vertices[k + (j - 6)];
}
// Compute the location's corresponding point on the cell in tile local coordinates,
// given the fractional portion of the parameterized s and t coordinates. These values indicate the location's
// relative placement within the cell. The cell's vertices are defined in the following order: lower-left,
// lower-right, upper-left, upper-right. The cell's diagonal starts at the lower-right vertex and ends at the
// upper-left vertex.
sf = s < tileWidth ? s - Math.floor(s) : 1;
tf = t < tileHeight ? t - Math.floor(t) : 1;
if (sf > tf) {
result[0] = points[0] + sf * (points[3] - points[0]) + tf * (points[6] - points[0]);
result[1] = points[1] + sf * (points[4] - points[1]) + tf * (points[7] - points[1]);
result[2] = points[2] + sf * (points[5] - points[2]) + tf * (points[8] - points[2]);
}
else {
result[0] = points[9] + (1 - sf) * (points[6] - points[9]) + (1 - tf) * (points[3] - points[9]);
result[1] = points[10] + (1 - sf) * (points[7] - points[10]) + (1 - tf) * (points[4] - points[10]);
result[2] = points[11] + (1 - sf) * (points[8] - points[11]) + (1 - tf) * (points[5] - points[11]);
}
result[0] += this.referencePoint[0];
result[1] += this.referencePoint[1];
result[2] += this.referencePoint[2];
return result;
};
TerrainTile.prototype.update = function (dc) {
Tile.prototype.update.call(this, dc);
var elevationTimestamp = dc.globe.elevationTimestamp();
if (this._elevationTimestamp != elevationTimestamp) {
this._elevationTimestamp = elevationTimestamp;
this._stateKey = null; // cause updates to any elevation-dependent cached state
}
};
// Intentionally not documented.
TerrainTile.prototype.computeStateKey = function () {
var array = [];
array.push(this._elevationTimestamp);
array.push(this.neighborMap[WorldWind.NORTH] ? this.neighborMap[WorldWind.NORTH].compare(this.level) : 0);
array.push(this.neighborMap[WorldWind.SOUTH] ? this.neighborMap[WorldWind.SOUTH].compare(this.level) : 0);
array.push(this.neighborMap[WorldWind.EAST] ? this.neighborMap[WorldWind.EAST].compare(this.level) : 0);
array.push(this.neighborMap[WorldWind.WEST] ? this.neighborMap[WorldWind.WEST].compare(this.level) : 0);
return array.join(".");
};
export default TerrainTile;

View File

@ -0,0 +1,81 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TerrainTileList
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Sector from '../geom/Sector';
/**
* Constructs a terrain tile list, a container for terrain tiles that also has a tessellator and a sector
* associated with it.
* @alias TerrainTileList
* @constructor
* @classdesc Represents a portion of a globe's terrain.
* @param {Tessellator} tessellator The tessellator that created this terrain tile list.
*
*/
function TerrainTileList(tessellator) {
if (!tessellator) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTileList", "TerrainTileList", "missingTessellator"));
}
this.tessellator = tessellator;
this.sector = null;
this.tileArray = [];
}
Object.defineProperties(TerrainTileList.prototype, {
/**
* The number of terrain tiles in this terrain tile list.
* @memberof TerrainTileList.prototype
* @readonly
* @type {Number}
*/
length: {
get: function () {
return this.tileArray.length;
}
}
});
TerrainTileList.prototype.addTile = function (tile) {
if (!tile) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTileList", "addTile", "missingTile"));
}
if (this.tileArray.indexOf(tile) == -1) {
this.tileArray.push(tile);
if (!this.sector) {
this.sector = new Sector(0, 0, 0, 0);
this.sector.copy(tile.sector);
} else {
this.sector.union(tile.sector);
}
}
};
TerrainTileList.prototype.removeAllTiles = function () {
this.tileArray = [];
this.sector = null;
};
export default TerrainTileList;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,635 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TiledElevationCoverage
*/
import AbsentResourceList from '../util/AbsentResourceList';
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import ElevationCoverage from '../globe/ElevationCoverage';
import ElevationImage from '../globe/ElevationImage';
import LevelSet from '../util/LevelSet';
import Location from '../geom/Location';
import Logger from '../util/Logger';
import MemoryCache from '../cache/MemoryCache';
import Sector from '../geom/Sector';
import Tile from '../util/Tile';
import WWMath from '../util/WWMath';
/**
* Constructs a TiledElevationCoverage
* @alias TiledElevationCoverage
* @constructor
* @classdesc Represents the elevations for an area, often but not necessarily the whole globe.
* @param {{}} config Configuration properties for the coverage:
* <ul>
* <li>coverageSector: {Sector} The sector this coverage spans.</li>
* <li>resolution: {Number} The resolution of the coverage, in degrees. (To compute degrees from meters, divide the number of meters by the globe's radius to obtain radians and convert the result to degrees.)</li>
* <li>retrievalImageFormat: {String} The mime type of the elevation data retrieved by this coverage.</li>
* <li>minElevation (optional): {Number} The coverage's minimum elevation in meters.</li>
* <li>maxElevation (optional): {Number} Te coverage's maximum elevation in meters.</li>
* <li>urlBuilder (optional): {UrlBuilder} The factory to create URLs for elevation data requests.</li>
* <ul>
* @throws {ArgumentError} If any required configuration parameter is null or undefined.
*/
function TiledElevationCoverage(config) {
if (!config) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "constructor", "missingConfig"));
}
if (!config.coverageSector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "constructor", "missingSector"));
}
if (!config.resolution) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "constructor", "missingResolution"));
}
if (!config.retrievalImageFormat) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "constructor", "missingImageFormat"));
}
ElevationCoverage.call(this, config.resolution);
var firstLevelDelta = 360,
tileWidth = 256,
lastLevel = LevelSet.numLevelsForResolution(firstLevelDelta / tileWidth, config.resolution),
numLevels = Math.ceil(lastLevel); // match or exceed the specified resolution
/**
* The sector this coverage spans.
* @type {Sector}
* @readonly
*/
this.coverageSector = config.coverageSector;
/**
* The mime type to use when retrieving elevations.
* @type {String}
* @readonly
*/
this.retrievalImageFormat = config.retrievalImageFormat;
/**
* This coverage's minimum elevation in meters.
* @type {Number}
* @default 0
*/
this.minElevation = config.minElevation || 0;
/**
* This coverage's maximum elevation in meters.
* @type {Number}
*/
this.maxElevation = config.maxElevation || 0;
/**
* Indicates whether the data associated with this coverage is point data. A value of false
* indicates that the data is area data (pixel is area).
* @type {Boolean}
* @default true
*/
this.pixelIsPoint = true;
/**
* The {@link LevelSet} dividing this coverage's geographic domain into a multi-resolution, hierarchical
* collection of tiles.
* @type {LevelSet}
* @readonly
*/
this.levels = new LevelSet(this.coverageSector, new Location(firstLevelDelta, firstLevelDelta),
numLevels, tileWidth, tileWidth);
/**
* Internal use only
* The list of assembled tiles.
* @type {Array}
* @ignore
*/
this.currentTiles = [];
/**
* Internal use only
* A scratch sector for use in computations.
* @type {Sector}
* @ignore
*/
this.currentSector = new Sector(0, 0, 0, 0);
/**
* Internal use only
* A cache of elevation tiles.
* @type {MemoryCache}
* @ignore
*/
this.tileCache = new MemoryCache(1000000, 800000);
/**
* Internal use only
* A cache of elevations.
* @type {MemoryCache}
* @ignore
*/
this.imageCache = new MemoryCache(10000000, 8000000);
/**
* Controls how many concurrent tile requests are allowed for this coverage.
* @type {Number}
* @default WorldWind.configuration.coverageRetrievalQueueSize
*/
this.retrievalQueueSize = WorldWind.configuration.coverageRetrievalQueueSize;
/**
* Internal use only
* The list of elevation retrievals in progress.
* @type {Array}
* @ignore
*/
this.currentRetrievals = [];
/**
* Internal use only
* The list of resources pending acquisition.
* @type {Array}
* @ignore
*/
this.absentResourceList = new AbsentResourceList(3, 5e3);
/**
* Internal use only
* The factory to create URLs for data requests. This property is typically set in the constructor of child
* classes. See {@link WcsUrlBuilder} for a concrete example.
* @type {UrlBuilder}
* @ignore
*/
// this.urlBuilder = new UrlBuilder() || null;
this.urlBuilder = config.urlBuilder || null;
}
TiledElevationCoverage.prototype = Object.create(ElevationCoverage.prototype);
// Documented in super class
TiledElevationCoverage.prototype.minAndMaxElevationsForSector = function (sector, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "minAndMaxElevationsForSector", "missingSector"));
}
if (!result) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "minAndMaxElevationsForSector", "missingResult"));
}
var levelNumber = Math.log2(360 / (sector.maxLongitude - sector.minLongitude));
var tileSize = 360 / 2 ** levelNumber;
var column = (180 + sector.minLongitude) / tileSize;
var row = (180 - sector.maxLatitude) / tileSize;
var tileKey = Tile.computeTileKey(levelNumber, row, column);
var image = this.imageCache.entryForKey(tileKey);
if (!image || !image.hasData) {
return false;
}
result[0] = image.minElevation;
result[1] = image.maxElevation;
return true;
};
// Documented in super class
TiledElevationCoverage.prototype.elevationAtLocation = function (latitude, longitude) {
latitude = WWMath.mercatorLat(latitude);
if (!this.coverageSector.containsLocation(latitude, longitude)) {
return null; // location is outside the coverage's coverage
}
return this.pointElevationForLocation(latitude, longitude);
};
// Documented in super class
TiledElevationCoverage.prototype.elevationsForGrid = function (sector, numLat, numLon, result) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "elevationsForGrid", "missingSector"));
}
if (!numLat || !numLon || numLat < 1 || numLon < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledElevationCoverage", "elevationsForGrid",
"The specified number of latitudinal or longitudinal positions is less than one."));
}
var gridResolution = sector.deltaLatitude() / (numLat - 1) * Angle.DEGREES_TO_RADIANS;
var level = this.levels.levelForTexelSize(gridResolution);
if (this.pixelIsPoint) {
return this.pointElevationsForGrid(sector, numLat, numLon, level, result);
} else {
return this.areaElevationsForGrid(sector, numLat, numLon, level, result);
}
};
// Intentionally not documented.
TiledElevationCoverage.prototype.pointElevationForLocation = function (latitude, longitude) {
var level = this.levels.lastLevel(),
deltaLat = level.tileDelta.latitude,
deltaLon = level.tileDelta.longitude,
r = Tile.computeRow(deltaLat, latitude),
c = Tile.computeColumn(deltaLon, longitude),
tileKey,
image = null;
for (var i = level.levelNumber; i >= 0; i--) {
tileKey = Tile.computeTileKey(i, r, c);
image = this.imageCache.entryForKey(tileKey);
if (image) {
var elevation = image.elevationAtLocation(latitude, longitude);
return isNaN(elevation) ? null : elevation;
}
r = Math.floor(r / 2);
c = Math.floor(c / 2);
}
return null; // did not find a tile with an image
};
// Intentionally not documented.
TiledElevationCoverage.prototype.pointElevationsForGrid = function (sector, numLat, numLon, level, result) {
this.assembleTiles(level, sector, true);
if (this.currentTiles.length === 0) {
return false; // Sector is outside the coverage's coverage area. Do not modify the results array.
}
// Sort from lowest resolution to highest so that higher resolutions override lower resolutions in the
// loop below.
this.currentTiles.sort(function (tileA, tileB) {
return tileA.level.levelNumber - tileB.level.levelNumber;
});
for (var i = 0, len = this.currentTiles.length; i < len; i++) {
var image = this.imageCache.entryForKey(this.currentTiles[i].tileKey);
if (image) {
image.elevationsForGrid(sector, numLat, numLon, result);
}
}
return !result.includes(NaN); // true if the result array is fully populated.
};
// Internal. Returns elevations for a grid assuming pixel-is-area.
TiledElevationCoverage.prototype.areaElevationsForGrid = function (sector, numLat, numLon, level, result) {
var minLat = sector.minLatitude,
maxLat = sector.maxLatitude,
minLon = sector.minLongitude,
maxLon = sector.maxLongitude,
deltaLat = sector.deltaLatitude() / (numLat > 1 ? numLat - 1 : 1),
deltaLon = sector.deltaLongitude() / (numLon > 1 ? numLon - 1 : 1),
lat, lon, s, t,
latIndex, lonIndex, resultIndex = 0;
for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex += 1, lat += deltaLat) {
if (latIndex === numLat - 1) {
lat = maxLat; // explicitly set the last lat to the max latitude ensure alignment
}
for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex += 1, lon += deltaLon) {
if (lonIndex === numLon - 1) {
lon = maxLon; // explicitly set the last lon to the max longitude ensure alignment
}
if (isNaN(result[resultIndex])) {
if (this.coverageSector.containsLocation(lat, lon)) { // ignore locations outside of the model
s = (lon + 180) / 360;
t = (lat + 180) / 360;
this.areaElevationForCoord(s, t, level.levelNumber, result, resultIndex);
}
}
resultIndex++;
}
}
return !result.includes(NaN); // true if the result array is fully populated.
};
// Internal. Returns an elevation for a location assuming pixel-is-area.
TiledElevationCoverage.prototype.areaElevationForCoord = function (s, t, levelNumber, result, resultIndex) {
var level, levelWidth, levelHeight,
tMin, tMax,
vMin, vMax,
u, v,
x0, x1, y0, y1,
xf, yf,
retrieveTiles,
pixels = new Float64Array(4);
for (var i = levelNumber; i >= 0; i--) {
level = this.levels.level(i);
levelWidth = Math.round(level.tileWidth * 360 / level.tileDelta.longitude);
levelHeight = Math.round(level.tileHeight * 360 / level.tileDelta.latitude);
tMin = 1 / (2 * levelHeight);
tMax = 1 - tMin;
vMin = 0;
vMax = levelHeight - 1;
u = levelWidth * WWMath.fract(s); // wrap the horizontal coordinate
v = levelHeight * WWMath.clamp(t, tMin, tMax); // clamp the vertical coordinate to the level edge
x0 = WWMath.mod(Math.floor(u - 0.5), levelWidth);
x1 = WWMath.mod(x0 + 1, levelWidth);
y0 = WWMath.clamp(Math.floor(v - 0.5), vMin, vMax);
y1 = WWMath.clamp(y0 + 1, vMin, vMax);
xf = WWMath.fract(u - 0.5);
yf = WWMath.fract(v - 0.5);
retrieveTiles = i == levelNumber || i == 0;
if (this.lookupPixels(x0, x1, y0, y1, level, retrieveTiles, pixels)) {
if (ElevationImage.isNoData(pixels[0], pixels[1], pixels[2], pixels[3])) {
return false;
}
else {
result[resultIndex] = (1 - xf) * (1 - yf) * pixels[0] +
xf * (1 - yf) * pixels[1] +
(1 - xf) * yf * pixels[2] +
xf * yf * pixels[3];
return true;
}
}
}
return false;
};
// Internal. Bilinearly interpolates tile-image elevations.
TiledElevationCoverage.prototype.lookupPixels = function (x0, x1, y0, y1, level, retrieveTiles, result) {
var levelNumber = level.levelNumber,
tileWidth = level.tileWidth,
tileHeight = level.tileHeight,
row0 = Math.floor(y0 / tileHeight),
row1 = Math.floor(y1 / tileHeight),
col0 = Math.floor(x0 / tileWidth),
col1 = Math.floor(x1 / tileWidth),
r0c0, r0c1, r1c0, r1c1;
if (row0 == row1 && row0 == this.cachedRow && col0 == col1 && col0 == this.cachedCol) {
r0c0 = r0c1 = r1c0 = r1c1 = this.cachedImage; // use results from previous lookup
} else if (row0 == row1 && col0 == col1) {
r0c0 = this.lookupImage(levelNumber, row0, col0, retrieveTiles); // only need to lookup one image
r0c1 = r1c0 = r1c1 = r0c0; // re-use the single image
this.cachedRow = row0;
this.cachedCol = col0;
this.cachedImage = r0c0; // note the results for subsequent lookups
} else {
r0c0 = this.lookupImage(levelNumber, row0, col0, retrieveTiles);
r0c1 = this.lookupImage(levelNumber, row0, col1, retrieveTiles);
r1c0 = this.lookupImage(levelNumber, row1, col0, retrieveTiles);
r1c1 = this.lookupImage(levelNumber, row1, col1, retrieveTiles);
}
if (r0c0 && r0c1 && r1c0 && r1c1) {
result[0] = r0c0.pixel(x0 % tileWidth, y0 % tileHeight);
result[1] = r0c1.pixel(x1 % tileWidth, y0 % tileHeight);
result[2] = r1c0.pixel(x0 % tileWidth, y1 % tileHeight);
result[3] = r1c1.pixel(x1 % tileWidth, y1 % tileHeight);
return true;
}
return false;
};
// Internal. Intentionally not documented.
TiledElevationCoverage.prototype.lookupImage = function (levelNumber, row, column, retrieveTiles) {
var tileKey = Tile.computeTileKey(levelNumber, row, column),
image = this.imageCache.entryForKey(tileKey);
if (image == null && retrieveTiles) {
var tile = this.tileForLevel(levelNumber, row, column);
this.retrieveTileImage(tile);
}
return image;
};
// Intentionally not documented.
TiledElevationCoverage.prototype.assembleTiles = function (level, sector, retrieveTiles) {
this.currentTiles = [];
// Intersect the requested sector with the coverage's coverage area. This avoids attempting to assemble tiles
// that are outside the coverage area.
this.currentSector.copy(sector);
this.currentSector.intersection(this.coverageSector);
if (this.currentSector.isEmpty())
return; // sector is outside the coverage's coverage area
var deltaLat = level.tileDelta.latitude,
deltaLon = level.tileDelta.longitude,
firstRow = Tile.computeRow(deltaLat, this.currentSector.minLatitude),
lastRow = Tile.computeLastRow(deltaLat, this.currentSector.maxLatitude),
firstCol = Tile.computeColumn(deltaLon, this.currentSector.minLongitude),
lastCol = Tile.computeLastColumn(deltaLon, this.currentSector.maxLongitude);
for (var row = firstRow; row <= lastRow; row++) {
for (var col = firstCol; col <= lastCol; col++) {
this.addTileOrAncestor(level, row, col, retrieveTiles);
}
}
};
// Intentionally not documented.
TiledElevationCoverage.prototype.addTileOrAncestor = function (level, row, column, retrieveTiles) {
var tile = this.tileForLevel(level.levelNumber, row, column);
if (this.isTileImageInMemory(tile)) {
this.addToCurrentTiles(tile);
} else {
if (retrieveTiles) {
this.retrieveTileImage(tile);
}
if (level.isFirstLevel()) {
this.currentTiles.push(tile); // no ancestor tile to add
} else {
this.addAncestor(level, row, column, retrieveTiles);
}
}
};
// Intentionally not documented.
TiledElevationCoverage.prototype.addAncestor = function (level, row, column, retrieveTiles) {
var tile = null,
r = Math.floor(row / 2),
c = Math.floor(column / 2);
for (var i = level.levelNumber - 1; i >= 0; i--) {
tile = this.tileForLevel(i, r, c);
if (this.isTileImageInMemory(tile)) {
this.addToCurrentTiles(tile);
return;
}
r = Math.floor(r / 2);
c = Math.floor(c / 2);
}
// No ancestor tiles have an in-memory image. Retrieve the ancestor tile corresponding for the first level, and
// add it. We add the necessary tiles to provide coverage over the requested sector in order to accurately return
// whether or not this coverage has data for the entire sector.
this.addToCurrentTiles(tile);
if (retrieveTiles) {
this.retrieveTileImage(tile);
}
};
// Intentionally not documented.
TiledElevationCoverage.prototype.addToCurrentTiles = function (tile) {
this.currentTiles.push(tile);
};
// Intentionally not documented.
TiledElevationCoverage.prototype.tileForLevel = function (levelNumber, row, column) {
var tileKey = Tile.computeTileKey(levelNumber, row, column),
tile = this.tileCache.entryForKey(tileKey);
if (tile) {
return tile;
}
var level = this.levels.level(levelNumber),
sector = Tile.computeSector(level, row, column);
tile = new Tile(sector, level, row, column);
this.tileCache.putEntry(tileKey, tile, tile.size());
return tile;
};
// Intentionally not documented.
TiledElevationCoverage.prototype.isTileImageInMemory = function (tile) {
return this.imageCache.containsKey(tile.tileKey);
};
// Intentionally not documented.
TiledElevationCoverage.prototype.resourceUrlForTile = function (tile) {
return this.urlBuilder.urlForTile(tile, this.retrievalImageFormat);
};
// Intentionally not documented.
TiledElevationCoverage.prototype.retrieveTileImage = function (tile) {
if (this.currentRetrievals.indexOf(tile.tileKey) < 0) {
if (this.currentRetrievals.length > this.retrievalQueueSize) {
return;
}
var url = this.resourceUrlForTile(tile, this.retrievalImageFormat),
xhr = new XMLHttpRequest(),
elevationCoverage = this;
if (!url)
return;
xhr.open("GET", url + `&x=${tile.column}&y=${tile.row}&z=${tile.level.levelNumber + 1}`, true);
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
elevationCoverage.removeFromCurrentRetrievals(tile.tileKey);
var contentType = xhr.getResponseHeader("content-type");
if (xhr.status === 200) {
if (contentType === elevationCoverage.retrievalImageFormat
|| contentType === "text/plain"
|| contentType === "application/octet-stream") {
Logger.log(Logger.LEVEL_INFO, "Elevations retrieval succeeded: " + url);
elevationCoverage.loadElevationImage(tile, xhr);
elevationCoverage.absentResourceList.unmarkResourceAbsent(tile.tileKey);
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
window.dispatchEvent(e);
} else if (contentType === "text/xml") {
elevationCoverage.absentResourceList.markResourceAbsent(tile.tileKey);
Logger.log(Logger.LEVEL_WARNING,
"Elevations retrieval failed (" + xhr.statusText + "): " + url + ".\n "
+ String.fromCharCode.apply(null, new Uint8Array(xhr.response)));
} else {
elevationCoverage.absentResourceList.markResourceAbsent(tile.tileKey);
Logger.log(Logger.LEVEL_WARNING,
"Elevations retrieval failed: " + url + ". " + "Unexpected content type "
+ contentType);
}
} else {
elevationCoverage.absentResourceList.markResourceAbsent(tile.tileKey);
Logger.log(Logger.LEVEL_WARNING,
"Elevations retrieval failed (" + xhr.statusText + "): " + url);
}
}
};
xhr.onerror = function () {
elevationCoverage.removeFromCurrentRetrievals(tile.tileKey);
elevationCoverage.absentResourceList.markResourceAbsent(tile.tileKey);
Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval failed: " + url);
};
xhr.ontimeout = function () {
elevationCoverage.removeFromCurrentRetrievals(tile.tileKey);
elevationCoverage.absentResourceList.markResourceAbsent(tile.tileKey);
Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval timed out: " + url);
};
xhr.send(null);
this.currentRetrievals.push(tile.tileKey);
}
};
// Intentionally not documented
TiledElevationCoverage.prototype.removeFromCurrentRetrievals = function (tileKey) {
var index = this.currentRetrievals.indexOf(tileKey);
if (index > -1) {
this.currentRetrievals.splice(index, 1);
}
};
// Intentionally not documented.
TiledElevationCoverage.prototype.loadElevationImage = function (tile, xhr) {
var elevationImage = new ElevationImage(tile.sector, tile.tileWidth, tile.tileHeight),
geoTiff;
if (this.retrievalImageFormat === "application/bil16") {
elevationImage.imageData = new Int16Array(xhr.response);
elevationImage.size = elevationImage.imageData.length * 2;
} else if (this.retrievalImageFormat === "application/bil32") {
elevationImage.imageData = new Float32Array(xhr.response);
elevationImage.size = elevationImage.imageData.length * 4;
}
if (elevationImage.imageData) {
elevationImage.findMinAndMaxElevation();
this.imageCache.putEntry(tile.tileKey, elevationImage, elevationImage.size);
this.timestamp = Date.now();
}
};
export default TiledElevationCoverage;

View File

@ -0,0 +1,49 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports UsgsNedElevationCoverage
*/
import Sector from '../geom/Sector';
import TiledElevationCoverage from '../globe/TiledElevationCoverage';
import WmsUrlBuilder from '../util/WmsUrlBuilder';
/**
* Constructs an Earth elevation coverage using USGS NED data.
* @alias UsgsNedElevationCoverage
* @constructor
* @augments TiledElevationCoverage
* @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
*/
function UsgsNedElevationCoverage() {
// CONUS Extent: (-124.848974, 24.396308) - (-66.885444, 49.384358)
// TODO: Expand this extent to cover HI when the server NO_DATA value issue is resolved.
TiledElevationCoverage.call(this, {
coverageSector: new Sector(24.396308, 49.384358, -124.848974, -66.885444),
resolution: 0.000092592592593,
retrievalImageFormat: "application/bil16",
minElevation: -11000,
maxElevation: 8850,
urlBuilder: new WmsUrlBuilder("https://worldwind26.arc.nasa.gov/elev", "USGS-NED", "", "1.3.0")
});
this.displayName = "USGS NED Earth Elevation Coverage";
}
UsgsNedElevationCoverage.prototype = Object.create(TiledElevationCoverage.prototype);
export default UsgsNedElevationCoverage;

View File

@ -0,0 +1,49 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports UsgsNedHiElevationCoverage
*/
import Sector from '../geom/Sector';
import TiledElevationCoverage from '../globe/TiledElevationCoverage';
import WmsUrlBuilder from '../util/WmsUrlBuilder';
/**
* Constructs an Earth elevation coverage using USGS NED data.
* @alias UsgsNedHiElevationCoverage
* @constructor
* @augments TiledElevationCoverage
* @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
*/
function UsgsNedHiElevationCoverage() {
// Hawaii Extent: (-178.443593, 18.865460) - (-154.755792, 28.517269)
// TODO: Remove this class when the server NO_DATA value issue is resolved.
TiledElevationCoverage.call(this, {
coverageSector: new Sector(18.865460, 28.517269, -178.443593, -154.755792),
resolution: 0.000092592592593,
retrievalImageFormat: "application/bil16",
minElevation: -11000,
maxElevation: 8850,
urlBuilder: new WmsUrlBuilder("https://worldwind26.arc.nasa.gov/elev", "USGS-NED", "", "1.3.0")
});
this.displayName = "USGS NED Hawaii Elevation Coverage";
}
UsgsNedHiElevationCoverage.prototype = Object.create(TiledElevationCoverage.prototype);
export default UsgsNedHiElevationCoverage;

View File

@ -0,0 +1,313 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports AtmosphereLayer
*/
import GroundProgram from '../shaders/GroundProgram';
import Layer from '../layer/Layer';
import Matrix3 from '../geom/Matrix3';
import Sector from '../geom/Sector';
import SkyProgram from '../shaders/SkyProgram';
import SunPosition from '../util/SunPosition';
import Vec3 from '../geom/Vec3';
import WWUtil from '../util/WWUtil';
/**
* Constructs a layer showing the Earth's atmosphere.
* @alias AtmosphereLayer
* @constructor
* @classdesc Provides a layer showing the Earth's atmosphere.
* @param {URL} nightImageSource optional url for the night texture.
* @augments Layer
*/
function AtmosphereLayer(nightImageSource) {
Layer.call(this, "Atmosphere");
// The atmosphere layer is not pickable.
this.pickEnabled = false;
//Documented in defineProperties below.
this._nightImageSource = nightImageSource ||
WorldWind.configuration.baseUrl + 'images/dnb_land_ocean_ice_2012.png';
//Internal use only.
//The light direction in cartesian space, computed from the layer time or defaults to the eyePoint.
this._activeLightDirection = new Vec3(0, 0, 0);
this._fullSphereSector = Sector.FULL_SPHERE;
//Internal use only. Intentionally not documented.
this._skyData = {};
//Internal use only. The number of longitudinal points in the grid for the sky geometry.
this._skyWidth = 128;
//Internal use only. The number of latitudinal points in the grid for the sky geometry.
this._skyHeight = 128;
//Internal use only. Number of indices for the sky geometry.
this._numIndices = 0;
//Internal use only. Texture coordinate matrix used for the night texture.
this._texMatrix = Matrix3.fromIdentity();
//Internal use only. The night texture.
this._activeTexture = null;
}
AtmosphereLayer.prototype = Object.create(Layer.prototype);
Object.defineProperties(AtmosphereLayer.prototype, {
/**
* Url for the night texture.
* @memberof AtmosphereLayer.prototype
* @type {URL}
*/
nightImageSource: {
get: function () {
return this._nightImageSource;
},
set: function (value) {
this._nightImageSource = value;
}
}
});
// Documented in superclass.
AtmosphereLayer.prototype.doRender = function (dc) {
if (dc.globe.is2D()) {
return;
}
this.determineLightDirection(dc);
this.drawSky(dc);
this.drawGround(dc);
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.applySkyVertices = function (dc) {
var gl = dc.currentGlContext,
program = dc.currentProgram,
skyData = this._skyData,
skyPoints, vboId;
if (!skyData.verticesVboCacheKey) {
skyData.verticesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
}
vboId = dc.gpuResourceCache.resourceForKey(skyData.verticesVboCacheKey);
if (!vboId) {
skyPoints = this.assembleVertexPoints(dc, this._skyHeight, this._skyWidth, program.getAltitude());
vboId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
gl.bufferData(gl.ARRAY_BUFFER, skyPoints, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
dc.gpuResourceCache.putResource(skyData.verticesVboCacheKey, vboId,
skyPoints.length * 4);
dc.frameStatistics.incrementVboLoadCount(1);
}
else {
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
}
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.applySkyIndices = function (dc) {
var gl = dc.currentGlContext,
skyData = this._skyData,
skyIndices, vboId;
if (!skyData.indicesVboCacheKey) {
skyData.indicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
}
vboId = dc.gpuResourceCache.resourceForKey(skyData.indicesVboCacheKey);
if (!vboId) {
skyIndices = this.assembleTriStripIndices(this._skyWidth, this._skyHeight);
vboId = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, skyIndices, gl.STATIC_DRAW);
dc.frameStatistics.incrementVboLoadCount(1);
dc.gpuResourceCache.putResource(skyData.indicesVboCacheKey, vboId, skyIndices.length * 2);
}
else {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
}
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.drawSky = function (dc) {
var gl = dc.currentGlContext,
program = dc.findAndBindProgram(SkyProgram);
program.loadGlobeRadius(gl, dc.globe.equatorialRadius);
program.loadEyePoint(gl, dc.eyePoint);
program.loadVertexOrigin(gl, Vec3.ZERO);
program.loadModelviewProjection(gl, dc.modelviewProjection);
program.loadLightDirection(gl, this._activeLightDirection);
program.setScale(gl);
this.applySkyVertices(dc);
this.applySkyIndices(dc);
gl.depthMask(false);
gl.frontFace(gl.CW);
gl.enableVertexAttribArray(0);
gl.drawElements(gl.TRIANGLE_STRIP, this._numIndices, gl.UNSIGNED_SHORT, 0);
gl.depthMask(true);
gl.frontFace(gl.CCW);
gl.disableVertexAttribArray(0);
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.drawGround = function (dc) {
var gl = dc.currentGlContext,
program = dc.findAndBindProgram(GroundProgram),
terrain = dc.terrain,
textureBound;
program.loadGlobeRadius(gl, dc.globe.equatorialRadius);
program.loadEyePoint(gl, dc.eyePoint);
program.loadLightDirection(gl, this._activeLightDirection);
program.setScale(gl);
// Use this layer's night image when the layer has time value defined
if (this.nightImageSource && this.time !== null) {
this._activeTexture = dc.gpuResourceCache.resourceForKey(this.nightImageSource);
if (!this._activeTexture) {
this._activeTexture = dc.gpuResourceCache.retrieveTexture(gl, this.nightImageSource);
}
textureBound = this._activeTexture && this._activeTexture.bind(dc);
}
terrain.beginRendering(dc);
for (var idx = 0, len = terrain.surfaceGeometry.length; idx < len; idx++) {
var currentTile = terrain.surfaceGeometry[idx];
// Use the vertex origin for the terrain tile.
var terrainOrigin = currentTile.referencePoint;
program.loadVertexOrigin(gl, terrainOrigin);
// Use a tex coord matrix that registers the night texture correctly on each terrain.
if (textureBound) {
this._texMatrix.setToUnitYFlip();
this._texMatrix.multiplyByTileTransform(currentTile.sector, this._fullSphereSector);
program.loadTexMatrix(gl, this._texMatrix);
}
terrain.beginRenderingTile(dc, currentTile);
// Draw the tile, multiplying the current fragment color by the program's secondary color.
program.loadFragMode(gl, program.FRAGMODE_GROUND_SECONDARY);
gl.blendFunc(gl.DST_COLOR, gl.ZERO);
terrain.renderTile(dc, currentTile);
// Draw the terrain as triangles, adding the current fragment color to the program's primary color.
var fragMode = textureBound ?
program.FRAGMODE_GROUND_PRIMARY_TEX_BLEND : program.FRAGMODE_GROUND_PRIMARY;
program.loadFragMode(gl, fragMode);
gl.blendFunc(gl.ONE, gl.ONE);
terrain.renderTile(dc, currentTile);
terrain.endRenderingTile(dc, currentTile);
}
// Restore the default WorldWind OpenGL state.
terrain.endRendering(dc);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
// Clear references to Gpu resources.
this._activeTexture = null;
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.assembleVertexPoints = function (dc, numLat, numLon, altitude) {
var count = numLat * numLon;
var altitudes = new Array(count);
WWUtil.fillArray(altitudes, altitude);
var result = new Float32Array(count * 3);
return dc.globe.computePointsForGrid(this._fullSphereSector, numLat, numLon, altitudes, Vec3.ZERO, result);
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.assembleTriStripIndices = function (numLat, numLon) {
var result = [];
var vertex = 0;
for (var latIndex = 0; latIndex < numLat - 1; latIndex++) {
// Create a triangle strip joining each adjacent column of vertices, starting in the bottom left corner and
// proceeding to the right. The first vertex starts with the left row of vertices and moves right to create
// a counterclockwise winding order.
for (var lonIndex = 0; lonIndex < numLon; lonIndex++) {
vertex = lonIndex + latIndex * numLon;
result.push(vertex + numLon);
result.push(vertex);
}
// Insert indices to create 2 degenerate triangles:
// - one for the end of the current row, and
// - one for the beginning of the next row
if (latIndex < numLat - 2) {
result.push(vertex);
result.push((latIndex + 2) * numLon);
}
}
this._numIndices = result.length;
return new Uint16Array(result);
};
// Internal. Intentionally not documented.
AtmosphereLayer.prototype.determineLightDirection = function (dc) {
if (this.time !== null) {
var sunLocation = SunPosition.getAsGeographicLocation(this.time);
dc.globe.computePointFromLocation(sunLocation.latitude, sunLocation.longitude,
this._activeLightDirection);
} else {
this._activeLightDirection.copy(dc.eyePoint);
}
this._activeLightDirection.normalize();
};
export default AtmosphereLayer;

View File

@ -0,0 +1,158 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Layer
*/
/**
* Constructs a layer. This constructor is meant to be called by subclasses and not directly by an application.
* @alias Layer
* @constructor
* @classdesc Provides an abstract base class for layer implementations. This class is not meant to be instantiated
* directly.
*/
function Layer(displayName) {
/**
* This layer's display name.
* @type {String}
* @default "Layer"
*/
this.displayName = displayName ? displayName : "Layer";
/**
* Indicates whether to display this layer.
* @type {Boolean}
* @default true
*/
this.enabled = true;
/**
* Indicates whether this layer is pickable.
* @type {Boolean}
* @default true
*/
this.pickEnabled = true;
/**
* This layer's opacity, which is combined with the opacity of shapes within layers.
* Opacity is in the range [0, 1], with 1 indicating fully opaque.
* @type {Number}
* @default 1
*/
this.opacity = 1;
/**
* The eye altitude above which this layer is displayed, in meters.
* @type {Number}
* @default -Number.MAX_VALUE (always displayed)
*/
this.minActiveAltitude = -Number.MAX_VALUE;
/**
* The eye altitude below which this layer is displayed, in meters.
* @type {Number}
* @default Number.MAX_VALUE (always displayed)
*/
this.maxActiveAltitude = Number.MAX_VALUE;
/**
* Indicates whether elements of this layer were drawn in the most recently generated frame.
* @type {Boolean}
* @readonly
*/
this.inCurrentFrame = false;
/**
* The time to display. This property selects the layer contents that represents the specified time.
* If null, layer-type dependent contents are displayed.
* @type {Date}
*/
this.time = null;
}
/**
* Refreshes the data associated with this layer. The behavior of this function varies with the layer
* type. For image layers, it causes the images to be re-retrieved from their origin.
*/
Layer.prototype.refresh = function () {
// Default implementation does nothing.
};
/**
* Displays this layer. Subclasses should generally not override this method but should instead override the
* [doRender]{@link Layer#doRender} method. This method calls that method after verifying that the layer is
* enabled, the eye point is within this layer's active altitudes and the layer is in view.
* @param {DrawContext} dc The current draw context.
*/
Layer.prototype.render = function (dc) {
this.inCurrentFrame = false;
if (!this.enabled)
return;
if (dc.pickingMode && !this.pickEnabled)
return;
if (!this.withinActiveAltitudes(dc))
return;
if (!this.isLayerInView(dc))
return;
this.doRender(dc);
};
/**
* Subclass method called to display this layer. Subclasses should implement this method rather than the
* [render]{@link Layer#render} method, which determines enable, pick and active altitude status and does not
* call this doRender method if the layer should not be displayed.
* @param {DrawContext} dc The current draw context.
* @protected
*/
Layer.prototype.doRender = function (dc) {
// Default implementation does nothing.
};
/* INTENTIONALLY NOT DOCUMENTED
* Indicates whether the current eye distance is within this layer's active-altitude range.
* @param {DrawContext} dc The current draw context.
* @returns {boolean} true If the eye distance is greater than or equal to this layer's minimum active
* altitude and less than or equal to this layer's maximum active altitude, otherwise false.
* @protected
*/
Layer.prototype.withinActiveAltitudes = function (dc) {
var eyePosition = dc.eyePosition;
if (!eyePosition)
return false;
return eyePosition.altitude >= this.minActiveAltitude && eyePosition.altitude <= this.maxActiveAltitude;
};
/**
* Indicates whether this layer is within the current view. Subclasses may override this method and
* when called determine whether the layer contents are visible in the current view frustum. The default
* implementation always returns true.
* @param {DrawContext} dc The current draw context.
* @returns {boolean} true If this layer is within the current view, otherwise false.
* @protected
*/
Layer.prototype.isLayerInView = function (dc) {
return true; // default implementation always returns true
};
export default Layer;

View File

@ -0,0 +1,67 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports MercatorTiledImageLayer
*/
import Sector from '../geom/Sector';
import TiledImageLayer from '../layer/TiledImageLayer';
import Vec2 from '../geom/Vec2';
import WWMath from '../util/WWMath';
/**
* Constructs a layer supporting Mercator imagery.
* @alias MercatorTiledImageLayer
* @constructor
* @augments TiledImageLayer
* @classdesc Provides an abstract layer to support Mercator layers.
*
* @param {Sector} sector The sector this layer covers.
* @param {Location} levelZeroDelta The size in latitude and longitude of level zero (lowest resolution) tiles.
* @param {Number} numLevels The number of levels to define for the layer. Each level is successively one power
* of two higher resolution than the next lower-numbered level. (0 is the lowest resolution level, 1 is twice
* that resolution, etc.)
* Each level contains four times as many tiles as the next lower-numbered level, each 1/4 the geographic size.
* @param {String} imageFormat The mime type of the image format for the layer's tiles, e.g., <em>image/png</em>.
* @param {String} cachePath A string uniquely identifying this layer relative to other layers.
* @param {Number} tileWidth The horizontal size of image tiles in pixels.
* @param {Number} tileHeight The vertical size of image tiles in pixels.
* @throws {ArgumentError} If any of the specified sector, level-zero delta, cache path or image format arguments are
* null or undefined, or if the specified number of levels, tile width or tile height is less than 1.
*/
function MercatorTiledImageLayer(sector, levelZeroDelta, numLevels, imageFormat, cachePath,
tileWidth, tileHeight) {
TiledImageLayer.call(this,
sector, levelZeroDelta, numLevels, imageFormat, cachePath, tileWidth, tileHeight);
}
MercatorTiledImageLayer.prototype = Object.create(TiledImageLayer.prototype);
// Overridden from TiledImageLayer. Computes a tile's sector and creates the tile.
// Unlike typical tiles, Tiles at the same level do not have the same sector size.
MercatorTiledImageLayer.prototype.createTile = function (sector, level, row, column) {
var degreePerTile = 360 / (1 << level.levelNumber);
var minLon = degreePerTile * column - 180;
var maxLon = minLon + degreePerTile;
var maxLat = 180 - degreePerTile * row;
var minLat = maxLat - degreePerTile;
sector = new Sector(minLat, maxLat, minLon, maxLon);
return TiledImageLayer.prototype.createTile.call(this, sector, level, row, column);
};
export default MercatorTiledImageLayer;

View File

@ -0,0 +1,428 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports StarFieldLayer
*/
import Layer from './Layer';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import StarFieldProgram from '../shaders/StarFieldProgram';
import SunPosition from '../util/SunPosition';
/**
* Constructs a layer showing stars and the Sun around the Earth.
* If used together with the AtmosphereLayer, the StarFieldLayer must be inserted before the AtmosphereLayer.
*
* If you want to use your own star data, the file provided must be .json
* and the fields 'ra', 'dec' and 'vmag' must be present in the metadata.
* ra and dec must be expressed in degrees.
*
* This layer uses J2000.0 as the ref epoch.
*
* If the star data .json file is too big, consider enabling gzip compression on your web server.
* For more info about enabling gzip compression consult the configuration for your web server.
*
* @alias StarFieldLayer
* @constructor
* @classdesc Provides a layer showing stars, and the Sun around the Earth
* @param {URL} starDataSource optional url for the stars data
* @augments Layer
*/
function StarFieldLayer(starDataSource) {
Layer.call(this, 'StarField');
// The StarField Layer is not pickable.
this.pickEnabled = false;
/**
* The size of the Sun in pixels.
* This can not exceed the maximum allowed pointSize of the GPU.
* A warning will be given if the size is too big and the allowed max size will be used.
* @type {Number}
* @default 128
*/
this.sunSize = 128;
/**
* Indicates weather to show or hide the Sun
* @type {Boolean}
* @default true
*/
this.showSun = true;
//Documented in defineProperties below.
this._starDataSource = starDataSource || WorldWind.configuration.baseUrl + 'images/stars.json';
this._sunImageSource = WorldWind.configuration.baseUrl + 'images/sunTexture.png';
//Internal use only.
//The MVP matrix of this layer.
this._matrix = Matrix.fromIdentity();
//Internal use only.
//gpu cache key for the stars vbo.
this._starsPositionsVboCacheKey = null;
//Internal use only.
this._numStars = 0;
//Internal use only.
this._starData = null;
//Internal use only.
this._minMagnitude = Number.MAX_VALUE;
this._maxMagnitude = Number.MIN_VALUE;
//Internal use only.
//A flag to indicate the star data is currently being retrieved.
this._loadStarted = false;
//Internal use only.
this._minScale = 10e6;
//Internal use only.
this._sunPositionsCacheKey = '';
this._sunBufferView = new Float32Array(4);
//Internal use only.
this._MAX_GL_POINT_SIZE = 0;
}
StarFieldLayer.prototype = Object.create(Layer.prototype);
Object.defineProperties(StarFieldLayer.prototype, {
/**
* Url for the stars data.
* @memberof StarFieldLayer.prototype
* @type {URL}
*/
starDataSource: {
get: function () {
return this._starDataSource;
},
set: function (value) {
this._starDataSource = value;
this.invalidateStarData();
}
},
/**
* Url for the sun texture image.
* @memberof StarFieldLayer.prototype
* @type {URL}
*/
sunImageSource: {
get: function () {
return this._sunImageSource;
},
set: function (value) {
this._sunImageSource = value;
}
}
});
// Documented in superclass.
StarFieldLayer.prototype.doRender = function (dc) {
if (dc.globe.is2D()) {
return;
}
if (!this.haveResources(dc)) {
this.loadResources(dc);
return;
}
this.beginRendering(dc);
try {
this.doDraw(dc);
}
finally {
this.endRendering(dc);
}
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.haveResources = function (dc) {
var sunTexture = dc.gpuResourceCache.resourceForKey(this._sunImageSource);
return (
this._starData != null &&
sunTexture != null
);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.loadResources = function (dc) {
var gl = dc.currentGlContext;
var gpuResourceCache = dc.gpuResourceCache;
if (!this._starData) {
this.fetchStarData();
}
var sunTexture = gpuResourceCache.resourceForKey(this._sunImageSource);
if (!sunTexture) {
gpuResourceCache.retrieveTexture(gl, this._sunImageSource);
}
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.beginRendering = function (dc) {
var gl = dc.currentGlContext;
dc.findAndBindProgram(StarFieldProgram);
gl.enableVertexAttribArray(0);
gl.depthMask(false);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.doDraw = function (dc) {
this.loadCommonUniforms(dc);
this.renderStars(dc);
if (this.showSun) {
this.renderSun(dc);
}
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.loadCommonUniforms = function (dc) {
var gl = dc.currentGlContext;
var program = dc.currentProgram;
var eyePoint = dc.eyePoint;
var eyePosition = dc.globe.computePositionFromPoint(eyePoint[0], eyePoint[1], eyePoint[2], {});
var scale = Math.max(eyePosition.altitude * 1.5, this._minScale);
this._matrix.copy(dc.modelviewProjection);
this._matrix.multiplyByScale(scale, scale, scale);
program.loadModelviewProjection(gl, this._matrix);
//this subtraction does not work properly on the GPU, it must be done on the CPU
//possibly due to precision loss
//number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
var julianDate = SunPosition.computeJulianDate(this.time || new Date());
program.loadNumDays(gl, julianDate - 2451545.0);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.renderStars = function (dc) {
var gl = dc.currentGlContext;
var gpuResourceCache = dc.gpuResourceCache;
var program = dc.currentProgram;
if (!this._starsPositionsVboCacheKey) {
this._starsPositionsVboCacheKey = gpuResourceCache.generateCacheKey();
}
var vboId = gpuResourceCache.resourceForKey(this._starsPositionsVboCacheKey);
if (!vboId) {
vboId = gl.createBuffer();
var positions = this.createStarsGeometry();
gpuResourceCache.putResource(this._starsPositionsVboCacheKey, vboId, positions.length * 4);
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
}
else {
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
}
dc.frameStatistics.incrementVboLoadCount(1);
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0);
program.loadMagnitudeRange(gl, this._minMagnitude, this._maxMagnitude);
program.loadTextureEnabled(gl, false);
gl.drawArrays(gl.POINTS, 0, this._numStars);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.renderSun = function (dc) {
var gl = dc.currentGlContext;
var program = dc.currentProgram;
var gpuResourceCache = dc.gpuResourceCache;
if (!this._MAX_GL_POINT_SIZE) {
this._MAX_GL_POINT_SIZE = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)[1];
}
if (this.sunSize > this._MAX_GL_POINT_SIZE) {
Logger.log(Logger.LEVEL_WARNING, 'StarFieldLayer - sunSize is to big, max size allowed is: ' +
this._MAX_GL_POINT_SIZE);
}
var sunCelestialLocation = SunPosition.getAsCelestialLocation(this.time || new Date());
//.x = declination
//.y = right ascension
//.z = point size
//.w = magnitude
this._sunBufferView[0] = sunCelestialLocation.declination;
this._sunBufferView[1] = sunCelestialLocation.rightAscension;
this._sunBufferView[2] = Math.min(this.sunSize, this._MAX_GL_POINT_SIZE);
this._sunBufferView[3] = 1;
if (!this._sunPositionsCacheKey) {
this._sunPositionsCacheKey = gpuResourceCache.generateCacheKey();
}
var vboId = gpuResourceCache.resourceForKey(this._sunPositionsCacheKey);
if (!vboId) {
vboId = gl.createBuffer();
gpuResourceCache.putResource(this._sunPositionsCacheKey, vboId, this._sunBufferView.length * 4);
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
gl.bufferData(gl.ARRAY_BUFFER, this._sunBufferView, gl.DYNAMIC_DRAW);
}
else {
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._sunBufferView);
}
dc.frameStatistics.incrementVboLoadCount(1);
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0);
program.loadTextureEnabled(gl, true);
var sunTexture = dc.gpuResourceCache.resourceForKey(this._sunImageSource);
sunTexture.bind(dc);
gl.drawArrays(gl.POINTS, 0, 1);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.endRendering = function (dc) {
var gl = dc.currentGlContext;
gl.depthMask(true);
gl.disableVertexAttribArray(0);
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.fetchStarData = function () {
if (this._loadStarted) {
return;
}
this._loadStarted = true;
var self = this;
var xhr = new XMLHttpRequest();
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
try {
self._starData = JSON.parse(this.response);
self.sendRedrawRequest();
}
catch (e) {
Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to parse JSON for star data ' +
e.toString());
}
}
else {
Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to fetch star data. Status: ' +
this.status + ' ' + this.statusText);
}
self._loadStarted = false;
};
xhr.onerror = function () {
Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to fetch star data');
self._loadStarted = false;
};
xhr.ontimeout = function () {
Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer fetch star data has timeout');
self._loadStarted = false;
};
xhr.open('GET', this._starDataSource, true);
xhr.send();
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.createStarsGeometry = function () {
var indexes = this.parseStarsMetadata(this._starData.metadata);
if (indexes.raIndex === -1) {
throw new Error(
Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
'Missing ra field in star data.'));
}
if (indexes.decIndex === -1) {
throw new Error(
Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
'Missing dec field in star data.'));
}
if (indexes.magIndex === -1) {
throw new Error(
Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
'Missing vmag field in star data.'));
}
var data = this._starData.data;
var positions = [];
this._minMagnitude = Number.MAX_VALUE;
this._maxMagnitude = Number.MIN_VALUE;
for (var i = 0, len = data.length; i < len; i++) {
var starInfo = data[i];
var declination = starInfo[indexes.decIndex]; //for latitude
var rightAscension = starInfo[indexes.raIndex]; //for longitude
var magnitude = starInfo[indexes.magIndex];
var pointSize = magnitude < 2 ? 2 : 1;
positions.push(declination, rightAscension, pointSize, magnitude);
this._minMagnitude = Math.min(this._minMagnitude, magnitude);
this._maxMagnitude = Math.max(this._maxMagnitude, magnitude);
}
this._numStars = Math.floor(positions.length / 4);
return positions;
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.parseStarsMetadata = function (metadata) {
var raIndex = -1,
decIndex = -1,
magIndex = -1;
for (var i = 0, len = metadata.length; i < len; i++) {
var starMetaInfo = metadata[i];
if (starMetaInfo.name === 'ra') {
raIndex = i;
}
if (starMetaInfo.name === 'dec') {
decIndex = i;
}
if (starMetaInfo.name === 'vmag') {
magIndex = i;
}
}
return {
raIndex: raIndex,
decIndex: decIndex,
magIndex: magIndex
};
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.invalidateStarData = function () {
this._starData = null;
this._starsPositionsVboCacheKey = null;
};
// Internal. Intentionally not documented.
StarFieldLayer.prototype.sendRedrawRequest = function () {
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
window.dispatchEvent(e);
};
export default StarFieldLayer;

View File

@ -0,0 +1,542 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TiledImageLayer
*/
import AbsentResourceList from '../util/AbsentResourceList';
import ArgumentError from '../error/ArgumentError';
import ImageTile from '../render/ImageTile';
import Layer from '../layer/Layer';
import LevelSet from '../util/LevelSet';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import MemoryCache from '../cache/MemoryCache';
import Texture from '../render/Texture';
import Tile from '../util/Tile';
import WWUtil from '../util/WWUtil';
import WWMath from '../util/WWMath';
/**
* Constructs a tiled image layer.
* @alias TiledImageLayer
* @constructor
* @classdesc
* Provides a layer that displays multi-resolution imagery arranged as adjacent tiles in a pyramid.
* This is the primary WorldWind base class for displaying imagery of this type. While it may be used as a
* stand-alone class, it is typically subclassed by classes that identify the remote image server.
* <p>
* While the image tiles for this class are typically drawn from a remote server such as a WMS server. The actual
* retrieval protocol is independent of this class and encapsulated by a class implementing the {@link UrlBuilder}
* interface and associated with instances of this class as a property.
* <p>
* There is no requirement that image tiles of this class be remote, they may be local or procedurally generated. For
* such cases the subclass overrides this class' [retrieveTileImage]{@link TiledImageLayer#retrieveTileImage} method.
* <p>
* Layers of this type are by default not pickable. Their pick-enabled flag is initialized to false.
*
* @augments Layer
* @param {Sector} sector The sector this layer covers.
* @param {Location} levelZeroDelta The size in latitude and longitude of level zero (lowest resolution) tiles.
* @param {Number} numLevels The number of levels to define for the layer. Each level is successively one power
* of two higher resolution than the next lower-numbered level. (0 is the lowest resolution level, 1 is twice
* that resolution, etc.)
* Each level contains four times as many tiles as the next lower-numbered level, each 1/4 the geographic size.
* @param {String} imageFormat The mime type of the image format for the layer's tiles, e.g., <em>image/png</em>.
* @param {String} cachePath A string uniquely identifying this layer relative to other layers.
* @param {Number} tileWidth The horizontal size of image tiles in pixels.
* @param {Number} tileHeight The vertical size of image tiles in pixels.
* @throws {ArgumentError} If any of the specified sector, level-zero delta, cache path or image format arguments are
* null or undefined, or if the specified number of levels, tile width or tile height is less than 1.
*
*/
function TiledImageLayer(sector, levelZeroDelta, numLevels, imageFormat, cachePath, tileWidth, tileHeight) {
if (!sector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor", "missingSector"));
}
if (!levelZeroDelta) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
"The specified level-zero delta is null or undefined."));
}
if (!imageFormat) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
"The specified image format is null or undefined."));
}
if (!cachePath) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
"The specified cache path is null or undefined."));
}
if (!numLevels || numLevels < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
"The specified number of levels is less than one."));
}
if (!tileWidth || !tileHeight || tileWidth < 1 || tileHeight < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
"The specified tile width or height is less than one."));
}
Layer.call(this, "Tiled Image Layer");
this.retrievalImageFormat = imageFormat;
this.cachePath = cachePath;
/**
* Controls how many concurrent tile requests are allowed for this layer.
* @type {Number}
* @default WorldWind.configuration.layerRetrievalQueueSize
*/
this.retrievalQueueSize = WorldWind.configuration.layerRetrievalQueueSize;
this.levels = new LevelSet(sector, levelZeroDelta, numLevels, tileWidth, tileHeight);
/**
* Controls the level of detail switching for this layer. The next highest resolution level is
* used when an image's texel size is greater than this number of pixels, up to the maximum resolution
* of this layer.
* @type {Number}
* @default 1.75
*/
this.detailControl = 3.5;
/**
* Indicates whether credentials are sent when requesting images from a different origin.
*
* Allowed values are anonymous and use-credentials.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin
* @type {string}
* @default anonymous
*/
this.crossOrigin = 'anonymous';
/* Intentionally not documented.
* Indicates the time at which this layer's imagery expire. Expired images are re-retrieved
* when the current time exceeds the specified expiry time. If null, images do not expire.
* @type {Date}
*/
this.expiration = null;
this.currentTiles = [];
this.currentTilesInvalid = true;
this.tileCache = new MemoryCache(500000, 400000);
this.currentRetrievals = [];
this.absentResourceList = new AbsentResourceList(3, 50e3);
this.pickEnabled = false;
// Internal. Intentionally not documented.
this.lasTtMVP = Matrix.fromIdentity();
}
TiledImageLayer.prototype = Object.create(Layer.prototype);
// Inherited from Layer.
TiledImageLayer.prototype.refresh = function () {
this.expiration = new Date();
this.currentTilesInvalid = true;
};
/**
* Initiates retrieval of this layer's level 0 images. Use
* [isPrePopulated]{@link TiledImageLayer#isPrePopulated} to determine when the images have been retrieved
* and associated with the level 0 tiles.
* Pre-populating is not required. It is used to eliminate the visual effect of loading tiles incrementally,
* but only for level 0 tiles. An application might pre-populate a layer in order to delay displaying it
* within a time series until all the level 0 images have been retrieved and added to memory.
* @param {WorldWindow} wwd The WorldWindow for which to pre-populate this layer.
* @throws {ArgumentError} If the specified WorldWindow is null or undefined.
*/
TiledImageLayer.prototype.prePopulate = function (wwd) {
if (!wwd) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "prePopulate", "missingWorldWindow"));
}
var dc = wwd.drawContext;
if (!this.topLevelTiles || this.topLevelTiles.length === 0) {
this.createTopLevelTiles(dc);
}
for (var i = 0; i < this.topLevelTiles.length; i++) {
var tile = this.topLevelTiles[i];
if (!this.isTileTextureInMemory(dc, tile)) {
this.retrieveTileImage(dc, tile, true); // suppress redraw upon successful retrieval
}
}
};
/**
* Initiates retrieval of this layer's tiles that are visible in the specified WorldWindow. Pre-populating is
* not required. It is used to eliminate the visual effect of loading tiles incrementally.
* @param {WorldWindow} wwd The WorldWindow for which to pre-populate this layer.
* @throws {ArgumentError} If the specified WorldWindow is null or undefined.
*/
TiledImageLayer.prototype.prePopulateCurrentTiles = function (wwd) {
if (!wwd) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "prePopulate", "missingWorldWindow"));
}
var dc = wwd.drawContext;
this.assembleTiles(dc);
for (var i = 0, len = this.currentTiles.length; i < len; i++) {
var tile = this.currentTiles[i];
if (!this.isTileTextureInMemory(dc, tile)) {
this.retrieveTileImage(dc, tile, true); // suppress redraw upon successful retrieval
}
}
};
/**
* Indicates whether this layer's level 0 tile images have been retrieved and associated with the tiles.
* Use [prePopulate]{@link TiledImageLayer#prePopulate} to initiate retrieval of level 0 images.
* @param {WorldWindow} wwd The WorldWindow associated with this layer.
* @returns {Boolean} true if all level 0 images have been retrieved, otherwise false.
* @throws {ArgumentError} If the specified WorldWindow is null or undefined.
*/
TiledImageLayer.prototype.isPrePopulated = function (wwd) {
if (!wwd) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "isPrePopulated", "missingWorldWindow"));
}
for (var i = 0; i < this.topLevelTiles.length; i++) {
if (!this.isTileTextureInMemory(wwd.drawContext, this.topLevelTiles[i])) {
return false;
}
}
return true;
};
// Intentionally not documented.
TiledImageLayer.prototype.createTile = function (sector, level, row, column) {
var path = this.cachePath + "-layer/" + level.levelNumber + "/" + row + "/" + column + "."
+ WWUtil.suffixForMimeType(this.retrievalImageFormat);
return new ImageTile(sector, level, row, column, path);
};
// Documented in superclass.
TiledImageLayer.prototype.doRender = function (dc) {
if (!dc.terrain)
return;
if (this.currentTilesInvalid
|| !dc.modelviewProjection.equals(this.lasTtMVP)
|| dc.globeStateKey !== this.lastGlobeStateKey) {
this.currentTilesInvalid = false;
// Tile fading works visually only when the surface tiles are opaque, otherwise the surface flashes
// when two tiles are drawn over the same area, even though one of them is semi-transparent.
// So do not provide fading when the surface opacity is less than 1;
if (dc.surfaceOpacity >= 1 && this.opacity >= 1) {
// Fading of outgoing tiles requires determination of the those tiles. Prepare an object with all of
// the preceding frame's tiles so that we can subsequently compare the list of newly selected tiles
// with the previously selected tiles.
this.previousTiles = {};
for (var j = 0; j < this.currentTiles.length; j++) {
this.previousTiles[this.currentTiles[j].imagePath] = this.currentTiles[j];
}
this.assembleTiles(dc);
this.fadeOutgoingTiles(dc);
} else {
this.assembleTiles(dc);
}
}
this.lasTtMVP.copy(dc.modelviewProjection);
this.lastGlobeStateKey = dc.globeStateKey;
if (this.currentTiles.length > 0) {
dc.surfaceTileRenderer.renderTiles(dc, this.currentTiles, this.opacity, dc.surfaceOpacity >= 1);
dc.frameStatistics.incrementImageTileCount(this.currentTiles.length);
this.inCurrentFrame = true;
}
};
TiledImageLayer.prototype.fadeOutgoingTiles = function (dc) {
// Determine which files are outgoing and fade their disappearance. Must be called after this frame's
// current tiles for this layer have been determined.
var visibilityDelta = (dc.timestamp - dc.previousRedrawTimestamp) / dc.fadeTime;
// Create a hash table of the current tiles so that we can check for tile inclusion below.
var current = {};
for (var i = 0; i < this.currentTiles.length; i++) {
var tile = this.currentTiles[i];
current[tile.imagePath] = tile;
}
// Determine whether the tile was in the previous frame but is not in this one. If that's the case,
// then the tile is outgoing and its opacity needs to be reduced.
for (var tileImagePath in this.previousTiles) {
if (this.previousTiles.hasOwnProperty(tileImagePath)) {
tile = this.previousTiles[tileImagePath];
if (tile.opacity > 0 && !current[tile.imagePath]) {
// Compute the reduced.
tile.opacity = Math.max(0, tile.opacity - visibilityDelta);
// If not fully faded, add the tile to the list of current tiles and request a redraw so that
// we'll be called continuously until all tiles have faded completely. Note that order in the
// current tiles list is important: the non-opaque tiles must be drawn after the opaque tiles.
if (tile.opacity > 0) {
this.currentTiles.push(tile);
this.currentTilesInvalid = true;
dc.redrawRequested = true;
}
}
}
}
};
// Documented in superclass.
TiledImageLayer.prototype.isLayerInView = function (dc) {
return dc.terrain && dc.terrain.sector && dc.terrain.sector.intersects(this.levels.sector);
};
// Documented in superclass.
TiledImageLayer.prototype.createTopLevelTiles = function (dc) {
this.topLevelTiles = [];
Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles);
};
// Intentionally not documented.
TiledImageLayer.prototype.assembleTiles = function (dc) {
this.currentTiles = [];
if (!this.topLevelTiles || this.topLevelTiles.length === 0) {
this.createTopLevelTiles(dc);
}
for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
var tile = this.topLevelTiles[i];
tile.update(dc);
this.currentAncestorTile = null;
if (this.isTileVisible(dc, tile)) {
this.addTileOrDescendants(dc, tile);
}
}
};
// Intentionally not documented.
TiledImageLayer.prototype.addTileOrDescendants = function (dc, tile) {
if (this.tileMeetsRenderingCriteria(dc, tile)) {
this.addTile(dc, tile);
return;
}
var ancestorTile = null;
try {
if (this.isTileTextureInMemory(dc, tile) || tile.level.levelNumber === 0) {
ancestorTile = this.currentAncestorTile;
this.currentAncestorTile = tile;
}
var nextLevel = this.levels.level(tile.level.levelNumber + 1),
subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);
for (var i = 0, len = subTiles.length; i < len; i++) {
var child = subTiles[i];
child.update(dc);
if (this.levels.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
this.addTileOrDescendants(dc, child);
}
}
} finally {
if (ancestorTile) {
this.currentAncestorTile = ancestorTile;
}
}
};
// Intentionally not documented.
TiledImageLayer.prototype.addTile = function (dc, tile) {
tile.fallbackTile = null;
var texture = dc.gpuResourceCache.resourceForKey(tile.imagePath);
if (texture) {
tile.opacity = 1;
this.currentTiles.push(tile);
// If the tile's texture has expired, cause it to be re-retrieved. Note that the current,
// expired texture is still used until the updated one arrives.
if (this.expiration && this.isTextureExpired(texture)) {
this.retrieveTileImage(dc, tile);
}
return;
}
this.retrieveTileImage(dc, tile);
if (this.currentAncestorTile) {
if (this.isTileTextureInMemory(dc, this.currentAncestorTile)) {
// Set up to map the ancestor tile into the current one.
tile.fallbackTile = this.currentAncestorTile;
tile.fallbackTile.opacity = 1;
this.currentTiles.push(tile);
}
}
};
// Intentionally not documented.
TiledImageLayer.prototype.isTileVisible = function (dc, tile) {
if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
return false;
}
return tile.extent.intersectsFrustum(dc.frustumInModelCoordinates);
};
// Intentionally not documented.
TiledImageLayer.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
var s = this.detailControl;
var highLatitude = WWMath.mercatorLat(75);
if (tile.sector.minLatitude >= highLatitude || tile.sector.maxLatitude <= -highLatitude) {
s *= 1.2;
}
return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
};
// Intentionally not documented.
TiledImageLayer.prototype.isTileTextureInMemory = function (dc, tile) {
return dc.gpuResourceCache.containsResource(tile.imagePath);
};
// Intentionally not documented.
TiledImageLayer.prototype.isTextureExpired = function (texture) {
return this.expiration && texture.creationTime.getTime() <= this.expiration.getTime();
};
/**
* Retrieves the image for the specified tile. Subclasses should override this method in order to retrieve,
* compute or otherwise create the image.
* @param {DrawContext} dc The current draw context.
* @param {ImageTile} tile The tile for which to retrieve the resource.
* @param {Boolean} suppressRedraw true to suppress generation of redraw events when an image is successfully
* retrieved, otherwise false.
* @protected
*/
TiledImageLayer.prototype.retrieveTileImage = function (dc, tile, suppressRedraw) {
if (this.currentRetrievals.indexOf(tile.imagePath) < 0) {
if (this.currentRetrievals.length > this.retrievalQueueSize) {
return;
}
if (this.absentResourceList.isResourceAbsent(tile.imagePath)) {
return;
}
var url = this.resourceUrlForTile(tile, this.retrievalImageFormat),
image = new Image(),
imagePath = tile.imagePath,
cache = dc.gpuResourceCache,
canvas = dc.currentGlContext.canvas,
layer = this;
if (!url) {
this.currentTilesInvalid = true;
return;
}
image.onload = function () {
Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + url);
var texture = layer.createTexture(dc, tile, image);
layer.removeFromCurrentRetrievals(imagePath);
if (texture) {
cache.putResource(imagePath, texture, texture.size);
layer.currentTilesInvalid = true;
layer.absentResourceList.unmarkResourceAbsent(imagePath);
if (!suppressRedraw) {
// Send an event to request a redraw.
var e = document.createEvent('Event');
e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
canvas.dispatchEvent(e);
}
}
};
image.onerror = function () {
layer.removeFromCurrentRetrievals(imagePath);
layer.absentResourceList.markResourceAbsent(imagePath);
Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + url);
};
this.currentRetrievals.push(imagePath);
image.crossOrigin = this.crossOrigin;
image.src = url;
}
};
// Intentionally not documented.
TiledImageLayer.prototype.createTexture = function (dc, tile, image) {
return new Texture(dc.currentGlContext, image);
};
// Intentionally not documented.
TiledImageLayer.prototype.removeFromCurrentRetrievals = function (imagePath) {
var index = this.currentRetrievals.indexOf(imagePath);
if (index > -1) {
this.currentRetrievals.splice(index, 1);
}
};
/**
* Returns the URL string for the resource.
* @param {ImageTile} tile The tile whose image is returned
* @param {String} imageFormat The mime type of the image format desired.
* @returns {String} The URL string, or null if the string can not be formed.
* @protected
*/
TiledImageLayer.prototype.resourceUrlForTile = function (tile, imageFormat) {
if (this.urlBuilder) {
return this.urlBuilder.urlForTile(tile, imageFormat);
} else {
return null;
}
};
export default TiledImageLayer;

View File

@ -0,0 +1,30 @@
import MercatorTiledImageLayer from './MercatorTiledImageLayer';
import Sector from '../geom/Sector';
import Location from '../geom/Location';
class URLBuilder {
urlForTile(tile, imageFormat) {
return `http://localhost:2020/api/Map/Tiles?x=${tile.column}&y=${tile.row}&z=${tile.level.levelNumber}`;
}
}
class XYZLayer extends MercatorTiledImageLayer {
constructor() {
let imageSize = 256;
let displayName = 'Bing';
super(new Sector(-180, 180, -180, 180), new Location(360, 360), 18, "image/jpeg",
displayName, imageSize, imageSize);
this.imageSize = imageSize;
this.displayName = displayName;
this.urlBuilder = new URLBuilder();
}
createTopLevelTiles(dc) {
this.topLevelTiles = [];
this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 0));
}
}
export default XYZLayer;

View File

@ -0,0 +1,54 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports LookAtNavigator
*/
import Location from '../geom/Location';
import Navigator from '../navigate/Navigator';
/**
* Constructs a look-at navigator.
* @alias LookAtNavigator
* @constructor
* @augments Navigator
* @classdesc Represents a navigator containing the required variables to enable the user to pan, zoom and tilt
* the globe.
*/
function LookAtNavigator() {
Navigator.call(this);
/**
* The geographic location at the center of the viewport.
* @type {Location}
*/
this.lookAtLocation = new Location(36.4, 117);
/**
* The distance from this navigator's eye point to its look-at location.
* @type {Number}
* @default 10,000 kilometers
*/
this.range = 10e6; // TODO: Compute initial range to fit globe in viewport.
// Development testing only. Set this to false to suppress default navigator limits on 2D globes.
this.enable2DLimits = true;
}
LookAtNavigator.prototype = Object.create(Navigator.prototype);
export default LookAtNavigator;

View File

@ -0,0 +1,53 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Navigator
*/
/**
* Constructs a base navigator.
* @alias Navigator
* @constructor
* @classdesc Provides an abstract base class for navigators. This class is not meant to be instantiated
* directly. See {@Link LookAtNavigator} for a concrete navigator.
*/
function Navigator() {
/**
* This navigator's heading, in degrees clockwise from north.
* @type {Number}
* @default 0
*/
this.heading = 0;
/**
* This navigator's tilt, in degrees.
* @type {Number}
* @default 0
*/
this.tilt = 0;
/**
* This navigator's roll, in degrees.
* @type {Number}
* @default 0
*/
this.roll = 0;
}
export default Navigator;

View File

@ -0,0 +1,78 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports PickedObject
*/
/**
* Constructs a picked object.
* @alias PickedObject
* @constructor
* @classdesc Represents a picked object.
* @param {Color} color The pick color identifying the object.
* @param {Object} userObject An object to associate with this picked object, usually the picked shape.
* @param {Position} position The picked object's geographic position. May be null if unknown.
* @param {Layer} parentLayer The layer containing the picked object.
* @param {Boolean} isTerrain true if the picked object is terrain, otherwise false.
*/
function PickedObject(color, userObject, position, parentLayer, isTerrain) {
/**
* This picked object's pick color.
* @type {Color}
* @readonly
*/
this.color = color;
/**
* The picked shape.
* @type {Object}
* @readonly
*/
this.userObject = userObject;
/**
* This picked object's geographic position.
* @type {Position}
* @readonly
*/
this.position = position;
/**
* The layer containing this picked object.
* @type {Layer}
* @readonly
*/
this.parentLayer = parentLayer;
/**
* Indicates whether this picked object is terrain.
* @type {Boolean}
* @readonly
*/
this.isTerrain = isTerrain;
/**
* Indicates whether this picked object is the top object.
* @type {boolean}
*/
this.isOnTop = false;
}
export default PickedObject;

View File

@ -0,0 +1,114 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports PickedObjectList
*/
/**
* Constructs a picked-object list.
* @alias PickedObjectList
* @constructor
* @classdesc Holds a collection of picked objects.
*/
function PickedObjectList() {
/**
* The picked objects.
* @type {Array}
*/
this.objects = [];
}
/**
* Indicates whether this list contains picked objects that are not terrain.
* @returns {Boolean} true if this list contains objects that are not terrain,
* otherwise false.
*/
PickedObjectList.prototype.hasNonTerrainObjects = function () {
return this.objects.length > 1 || this.objects.length === 1 && this.terrainObject() == null;
};
/**
* Returns the terrain object within this list, if this list contains a terrain object.
* @returns {PickedObject} The terrain object, or null if this list does not contain a terrain object.
*/
PickedObjectList.prototype.terrainObject = function () {
for (var i = 0, len = this.objects.length; i < len; i++) {
if (this.objects[i].isTerrain) {
return this.objects[i];
}
}
return null;
};
/**
* Adds a picked object to this list.
* If the picked object is a terrain object and the list already contains a terrain object, the terrain
* object in the list is replaced by the specified one.
* @param {PickedObject} pickedObject The picked object to add. If null, this list remains unchanged.
*/
PickedObjectList.prototype.add = function (pickedObject) {
if (pickedObject) {
if (pickedObject.isTerrain) {
var terrainObjectIndex = this.objects.length;
for (var i = 0, len = this.objects.length; i < len; i++) {
if (this.objects[i].isTerrain) {
terrainObjectIndex = i;
break;
}
}
this.objects[terrainObjectIndex] = pickedObject;
} else {
this.objects.push(pickedObject);
}
}
};
/**
* Removes all items from this list.
*/
PickedObjectList.prototype.clear = function () {
this.objects = [];
};
/**
* Returns the top-most picked object in this list.
* @returns {PickedObject} The top-most picked object in this list, or null if this list is empty.
*/
PickedObjectList.prototype.topPickedObject = function () {
var size = this.objects.length;
if (size > 1) {
for (var i = 0; i < size; i++) {
if (this.objects[i].isOnTop) {
return this.objects[i];
}
}
}
if (size > 0) {
return this.objects[0];
}
return null;
};
export default PickedObjectList;

View File

@ -0,0 +1,252 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GeographicProjection
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* Constructs a base geographic projection.
* @alias GeographicProjection
* @constructor
* @classdesc Represents a geographic projection.
* This is an abstract class and is meant to be instantiated only by subclasses.
* See the following projections:
* <ul>
* <li>{@link ProjectionWgs84}</li>
</ul>
* @param {String} displayName The projection's display name.
* @param {boolean} continuous Indicates whether this projection is continuous.
* @param {Sector} projectionLimits This projection's projection limits. May be null to indicate the full
* range of latitude and longitude, +/- 90 degrees latitude, +/- 180 degrees longitude.
*/
function GeographicProjection(displayName, continuous, projectionLimits) {
/**
* This projection's display name.
* @type {string}
*/
this.displayName = displayName || "Geographic Projection";
/**
* Indicates whether this projection should be treated as continuous with itself. If true, the 2D map
* will appear to scroll continuously horizontally.
* @type {boolean}
* @readonly
*/
this.continuous = continuous;
/**
* Indicates the geographic limits of this projection.
* @type {Sector}
* @readonly
*/
this.projectionLimits = projectionLimits;
/**
* Indicates whether this projection is a 2D projection.
* @type {boolean}
* @readonly
*/
this.is2D = true;
}
/**
* Converts a geographic position to Cartesian coordinates.
*
* @param {Globe} globe The globe this projection is applied to.
* @param {number} latitude The latitude of the position, in degrees.
* @param {number} longitude The longitude of the position, in degrees.
* @param {number} elevation The elevation of the position, in meters.
* @param {Vec3} offset An offset to apply to the Cartesian output. Typically only projections that are
* continuous (see [continuous]{@link GeographicProjection#continuous}) apply to this offset. Others ignore
* it. May be null to indicate no offset is applied.
* @param {Vec3} result A variable in which to store the computed Cartesian point.
*
* @returns {Vec3} The specified result argument containing the computed point.
* @throws {ArgumentError} If the specified globe or result is null or undefined.
*/
GeographicProjection.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
offset, result) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesian", "abstractInvocation"));
};
/**
* Computes a grid of Cartesian points within a specified sector and relative to a specified Cartesian
* reference point.
* <p>
* This method is used to compute a collection of points within a sector. It is used by tessellators to
* efficiently generate a tile's interior points. The number of points to generate is indicated by the tileWidth
* and tileHeight parameters but is one more in each direction. Width refers to the longitudinal direction,
* height to the latitudinal.
* <p>
* For each implied position within the sector, an elevation value is specified via an array of elevations. The
* calculation at each position incorporates the associated elevation.
* There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
*
* @param {Globe} globe The globe this projection applies to.
* @param {Sector} sector The sector in which to compute the points.
* @param {Number} numLat The number of latitudinal sections a tile is divided into.
* @param {Number} numLon The number of longitudinal sections a tile is divided into.
* @param {Number[]} elevations An array of elevations to incorporate in the point calculations. There must be
* one elevation value in the array for each generated point. Elevations are in meters.
* There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
* @param {Vec3} referencePoint The X, Y and Z Cartesian coordinates to subtract from the computed coordinates.
* This makes the computed coordinates relative to the specified point. May be null.
* @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
* are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
* May be null to indicate that no offset is applied.
* @param {Float32Array} result A typed array to hold the computed coordinates. It must be at least of
* size (tileWidth + 1) x (tileHeight + 1) * 3.
* The points are returned in row major order, beginning with the row of minimum latitude.
* @returns {Float32Array} The specified result argument, populated with the computed Cartesian coordinates.
* @throws {ArgumentError} if any of the specified globe, sector, elevations array or results arrays is null or
* undefined.
*/
GeographicProjection.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
referencePoint, offset, result) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesianGrid", "abstractInvocation"));
};
/**
* Converts a Cartesian point to a geographic position.
* @param {Globe} globe The globe this projection is applied to.
* @param {number} x The X component of the Cartesian point.
* @param {number} y The Y component of the Cartesian point.
* @param {number} z The Z component of the Cartesian point.
* @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
* are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
* May be null to indicate that no offset is applied.
* @param {Position} result A variable in which to return the computed position.
*
* @returns {Position} The specified result argument containing the computed position.
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
*/
GeographicProjection.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "cartesianToGeographic", "abstractInvocation"));
};
/**
* Computes a Cartesian vector that points north and is tangent to the meridian at a specified geographic
* location.
*
* @param {Globe} globe The globe this projection is applied to.
* @param {number} latitude The latitude of the location, in degrees.
* @param {number} longitude The longitude of the location, in degrees.
* @param {Vec3} result A variable in which to return the computed vector.
*
* @returns{Vec3} The specified result argument containing the computed vector.
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
*/
GeographicProjection.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection",
"northTangentAtLocation", "missingResult"));
}
result[0] = 0;
result[1] = 1;
result[2] = 0;
return result;
};
/**
* Computes a Cartesian vector that points north and is tangent to the meridian at a specified Cartesian
* point.
*
* @param {Globe} globe The globe this projection is applied to.
* @param {number} x The X component of the Cartesian point.
* @param {number} y The Y component of the Cartesian point.
* @param {number} z The Z component of the Cartesian point.
* @param {Vec3} offset An offset to apply to the Cartesian point. Typically only projections that
* are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
* May be null to indicate that no offset is applied.
* @param {Vec3} result A variable in which to return the computed vector.
*
* @returns{Vec3} The specified result argument containing the computed vector.
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
*/
GeographicProjection.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection",
"northTangentAtPoint", "missingResult"));
}
result[0] = 0;
result[1] = 1;
result[2] = 0;
return result;
};
/**
* Computes the Cartesian surface normal vector at a specified geographic location.
*
* @param {Globe} globe The globe this projection is applied to.
* @param {number} latitude The latitude of the location, in degrees.
* @param {number} longitude The longitude of the location, in degrees.
* @param {Vec3} result A variable in which to return the computed vector.
*
* @returns{Vec3} The specified result argument containing the computed vector.
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
*/
GeographicProjection.prototype.surfaceNormalAtLocation = function (globe, latitude, longitude, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "surfaceNormalAtLocation",
"missingResult"));
}
result[0] = 0;
result[1] = 0;
result[2] = 1;
return result;
};
/**
* Computes the Cartesian surface normal vector at a specified Cartesian point.
*
* @param {Globe} globe The globe this projection is applied to.
* @param {number} x The X component of the Cartesian point.
* @param {number} y The Y component of the Cartesian point.
* @param {number} z The Z component of the Cartesian point.
* @param {Vec3} result A variable in which to return the computed vector.
*
* @returns{Vec3} The specified result argument containing the computed vector.
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
*/
GeographicProjection.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "surfaceNormalAtPoint",
"missingResult"));
}
result[0] = 0;
result[1] = 0;
result[2] = 1;
return result;
};
export default GeographicProjection;

View File

@ -0,0 +1,311 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ProjectionWgs84
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import GeographicProjection from '../projections/GeographicProjection';
import Logger from '../util/Logger';
import Position from '../geom/Position';
import Vec3 from '../geom/Vec3';
import WWMath from '../util/WWMath';
/**
* Constructs a WGS84 ellipsoid
* @alias ProjectionWgs84
* @constructor
* @augments GeographicProjection
* @classdesc Represents a WGS84 ellipsoid.
*/
function ProjectionWgs84() {
GeographicProjection.call(this, "WGS84", false, null);
this.is2D = false;
this.scratchPosition = new Position(0, 0, 0);
}
ProjectionWgs84.prototype = Object.create(GeographicProjection.prototype);
Object.defineProperties(ProjectionWgs84.prototype, {
/**
* A string identifying this projection's current state. Used to compare states during rendering to
* determine whether globe-state dependent cached values must be updated. Applications typically do not
* interact with this property.
* @memberof GeographicProjection.prototype
* @readonly
* @type {String}
*/
stateKey: {
get: function () {
return "projection wgs84 ";
}
}
});
// Documented in base class.
ProjectionWgs84.prototype.geographicToCartesian = function (globe, latitude, longitude, altitude, offset,
result) {
if (!globe) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
"geographicToCartesian", "missingGlobe"));
}
// latitude = WWMath._mercatorLatInvert(latitude); // TODO
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS),
rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);
result[0] = (rpm + altitude) * cosLat * sinLon;
result[1] = (rpm * (1.0 - globe.eccentricitySquared) + altitude) * sinLat;
result[2] = (rpm + altitude) * cosLat * cosLon;
return result;
};
// Documented in base class.
ProjectionWgs84.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
referencePoint, offset, result) {
if (!globe) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
"geographicToCartesianGrid", "missingGlobe"));
}
var minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
latIndex, lonIndex,
elevIndex = 0, resultIndex = 0,
lat, lon, rpm, elev,
cosLat, sinLat,
cosLon = new Float64Array(numLon), sinLon = new Float64Array(numLon);
// Compute and save values that are a function of each unique longitude value in the specified sector. This
// eliminates the need to re-compute these values for each column of constant longitude.
for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
if (lonIndex === numLon - 1) {
lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
}
cosLon[lonIndex] = Math.cos(lon);
sinLon[lonIndex] = Math.sin(lon);
}
// Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian
// point corresponding to each latitude and longitude.
for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
if (latIndex === numLat - 1) {
lat = maxLat; // explicitly set the last lat to the max longitude to ensure alignment
}
var invertLat = WWMath._mercatorLatInvert(lat); // TODO
// Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
cosLat = Math.cos(invertLat);
sinLat = Math.sin(invertLat);
rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);
for (lonIndex = 0; lonIndex < numLon; lonIndex++) {
elev = elevations[elevIndex++];
result[resultIndex++] = (rpm + elev) * cosLat * sinLon[lonIndex] - refCenter[0];
result[resultIndex++] = (rpm * (1.0 - globe.eccentricitySquared) + elev) * sinLat - refCenter[1];
result[resultIndex++] = (rpm + elev) * cosLat * cosLon[lonIndex] - refCenter[2];
}
}
return result;
};
// Documented in base class.
ProjectionWgs84.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
if (!globe) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
"cartesianToGeographic", "missingGlobe"));
}
// According to H. Vermeille, "An analytical method to transform geocentric into geodetic coordinates"
// http://www.springerlink.com/content/3t6837t27t351227/fulltext.pdf
// Journal of Geodesy, accepted 10/2010, not yet published
var X = z,
Y = x,
Z = y,
XXpYY = X * X + Y * Y,
sqrtXXpYY = Math.sqrt(XXpYY),
a = globe.equatorialRadius,
ra2 = 1 / (a * a),
e2 = globe.eccentricitySquared,
e4 = e2 * e2,
p = XXpYY * ra2,
q = Z * Z * (1 - e2) * ra2,
r = (p + q - e4) / 6,
h,
phi,
u,
evoluteBorderTest = 8 * r * r * r + e4 * p * q,
rad1,
rad2,
rad3,
atan,
v,
w,
k,
D,
sqrtDDpZZ,
e,
lambda,
s2;
if (evoluteBorderTest > 0 || q != 0) {
if (evoluteBorderTest > 0) {
// Step 2: general case
rad1 = Math.sqrt(evoluteBorderTest);
rad2 = Math.sqrt(e4 * p * q);
// 10*e2 is my arbitrary decision of what Vermeille means by "near... the cusps of the evolute".
if (evoluteBorderTest > 10 * e2) {
rad3 = WWMath.cbrt((rad1 + rad2) * (rad1 + rad2));
u = r + 0.5 * rad3 + 2 * r * r / rad3;
}
else {
u = r + 0.5 * WWMath.cbrt((rad1 + rad2) * (rad1 + rad2))
+ 0.5 * WWMath.cbrt((rad1 - rad2) * (rad1 - rad2));
}
}
else {
// Step 3: near evolute
rad1 = Math.sqrt(-evoluteBorderTest);
rad2 = Math.sqrt(-8 * r * r * r);
rad3 = Math.sqrt(e4 * p * q);
atan = 2 * Math.atan2(rad3, rad1 + rad2) / 3;
u = -4 * r * Math.sin(atan) * Math.cos(Math.PI / 6 + atan);
}
v = Math.sqrt(u * u + e4 * q);
w = e2 * (u + v - q) / (2 * v);
k = (u + v) / (Math.sqrt(w * w + u + v) + w);
D = k * sqrtXXpYY / (k + e2);
sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
h = (k + e2 - 1) * sqrtDDpZZ / k;
phi = 2 * Math.atan2(Z, sqrtDDpZZ + D);
}
else {
// Step 4: singular disk
rad1 = Math.sqrt(1 - e2);
rad2 = Math.sqrt(e2 - p);
e = Math.sqrt(e2);
h = -a * rad1 * rad2 / e;
phi = rad2 / (e * rad2 + rad1 * Math.sqrt(p));
}
// Compute lambda
s2 = Math.sqrt(2);
if ((s2 - 1) * Y < sqrtXXpYY + X) {
// case 1 - -135deg < lambda < 135deg
lambda = 2 * Math.atan2(Y, sqrtXXpYY + X);
}
else if (sqrtXXpYY + Y < (s2 + 1) * X) {
// case 2 - -225deg < lambda < 45deg
lambda = -Math.PI * 0.5 + 2 * Math.atan2(X, sqrtXXpYY - Y);
}
else {
// if (sqrtXXpYY-Y<(s2=1)*X) { // is the test, if needed, but it's not
// case 3: - -45deg < lambda < 225deg
lambda = Math.PI * 0.5 - 2 * Math.atan2(X, sqrtXXpYY + Y);
}
result.latitude = Angle.RADIANS_TO_DEGREES * phi;
result.longitude = Angle.RADIANS_TO_DEGREES * lambda;
result.altitude = h;
return result;
};
ProjectionWgs84.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
// The north-pointing tangent is derived by rotating the vector (0, 1, 0) about the Y-axis by longitude degrees,
// then rotating it about the X-axis by -latitude degrees. The latitude angle must be inverted because latitude
// is a clockwise rotation about the X-axis, and standard rotation matrices assume counter-clockwise rotation.
// The combined rotation can be represented by a combining two rotation matrices Rlat, and Rlon, then
// transforming the vector (0, 1, 0) by the combined transform:
//
// NorthTangent = (Rlon * Rlat) * (0, 1, 0)
//
// This computation can be simplified and encoded inline by making two observations:
// - The vector's X and Z coordinates are always 0, and its Y coordinate is always 1.
// - Inverting the latitude rotation angle is equivalent to inverting sinLat. We know this by the
// trigonometric identities cos(-x) = cos(x), and sin(-x) = -sin(x).
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
result[0] = -sinLat * sinLon;
result[1] = cosLat;
result[2] = -sinLat * cosLon;
return result.normalize();
};
ProjectionWgs84.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
this.cartesianToGeographic(globe, x, y, z, Vec3.ZERO, this.scratchPosition);
return this.northTangentAtLocation(globe, this.scratchPosition.latitude, this.scratchPosition.longitude, result);
};
ProjectionWgs84.prototype.surfaceNormalAtLocation = function (globe, latitude, longitude, result) {
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
result[0] = cosLat * sinLon;
result[1] = sinLat;
result[2] = cosLat * cosLon;
return result.normalize();
};
ProjectionWgs84.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
if (!globe) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
"surfaceNormalAtPoint", "missingGlobe"));
}
var a2 = globe.equatorialRadius * globe.equatorialRadius,
b2 = globe.polarRadius * globe.polarRadius;
result[0] = x / a2;
result[1] = y / b2;
result[2] = z / a2;
return result.normalize();
};
export default ProjectionWgs84;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,147 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports FramebufferTexture
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import '../util/WWMath';
/**
* Constructs a framebuffer texture with the specified dimensions and an optional depth buffer. Use the
* [DrawContext.bindFramebuffer]{@link DrawContext#bindFramebuffer} function to make the program current during rendering.
*
* @alias FramebufferTexture
* @constructor
* @classdesc Represents an off-screen WebGL framebuffer. The framebuffer has color buffer stored in a 32
* bit RGBA texture, and has an optional depth buffer of at least 16 bits. Applications typically do not
* interact with this class. WebGL framebuffers are created by instances of this class and made current when the
* DrawContext.bindFramebuffer function is invoked.
* @param {WebGLRenderingContext} gl The current WebGL rendering context.
* @param {Number} width The width of the framebuffer, in pixels.
* @param {Number} height The height of the framebuffer, in pixels.
* @param {Boolean} depth true to configure the framebuffer with a depth buffer of at least 16 bits, false to
* disable depth buffering.
* @throws {ArgumentError} If the specified draw context is null or undefined, or if the width or height is less
* than zero.
*/
function FramebufferTexture(gl, width, height, depth) {
if (!gl) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTexture", "constructor",
"missingGlContext"));
}
if (width < 0 || height < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTexture", "constructor",
"The framebuffer width or height is less than zero."));
}
/**
* The width of this framebuffer, in pixels.
* @type {Number}
* @readonly
*/
this.width = width;
/**
* The height of this framebuffer, in pixels.
* @type {Number}
* @readonly
*/
this.height = height;
/**
* Indicates whether or not this framebuffer has a depth buffer.
* @type {Boolean}
* @readonly
*/
this.depth = depth;
/**
* Indicates the size of this framebuffer's WebGL resources, in bytes.
* @type {Number}
* @readonly
*/
this.size = width * height * 4 + (depth ? width * height * 2 : 0);
/**
* Indicates the WebGL framebuffer object object associated with this framebuffer texture.
* @type {WebGLFramebuffer}
* @readonly
*/
this.framebufferId = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebufferId);
// Internal. Intentionally not documented. Configure this framebuffer's color buffer.
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,
gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, this.texture, 0);
// Internal. Intentionally not documented. Configure this framebuffer's optional depth buffer.
this.depthBuffer = null;
if (depth) {
this.depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16,
width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER, this.depthBuffer);
}
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (e != gl.FRAMEBUFFER_COMPLETE) {
Logger.logMessage(Logger.LEVEL_WARNING, "FramebufferTexture", "constructor",
"Error creating framebuffer: " + e);
this.framebufferId = null;
this.texture = null;
this.depthBuffer = null;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
}
/**
* Binds this off-screen framebuffer's texture in the current WebGL graphics context. This texture contains
* color fragments resulting from WebGL operations executed when this framebuffer is bound by a call to
* [FramebufferTexture.bindFramebuffer]{@link FramebufferTexture#bindFramebuffer}.
*
* @param {DrawContext} dc The current draw context.
* @returns {Boolean} true if this framebuffer's texture was bound successfully, otherwise false.
*/
FramebufferTexture.prototype.bind = function (dc) {
if (this.texture) {
dc.currentGlContext.bindTexture(gl.TEXTURE_2D, this.texture);
}
return !!this.texture;
};
export default FramebufferTexture;

View File

@ -0,0 +1,128 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports FramebufferTile
*/
import ArgumentError from '../error/ArgumentError';
import FramebufferTexture from '../render/FramebufferTexture';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import Rectangle from '../geom/Rectangle';
import TextureTile from '../render/TextureTile';
/**
* Constructs a framebuffer tile.
* @alias FramebufferTile
* @constructor
* @augments TextureTile
* @classdesc Represents a WebGL framebuffer applied to a portion of a globe's terrain. The framebuffer's width
* and height in pixels are equal to this tile's [tileWidth]{@link FramebufferTile#tileWidth} and
* [tileHeight]{@link FramebufferTile#tileHeight}, respectively. The framebuffer can be made active by calling
* [bindFramebuffer]{@link FramebufferTile#bindFramebuffer}. Color fragments written to this
* tile's framebuffer can then be drawn on the terrain surface using a
* [SurfaceTileRenderer]{@link SurfaceTileRenderer}.
* <p>
* This class is meant to be used internally. Applications typically do not interact with this class.
* @param {Sector} sector The sector this tile covers.
* @param {Level} level The level this tile is associated with.
* @param {Number} row This tile's row in the associated level.
* @param {Number} column This tile's column in the associated level.
* @param {String} cacheKey A string uniquely identifying this tile relative to other tiles.
* @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
* are less than zero, or the cache name is null, undefined or empty.
*/
function FramebufferTile(sector, level, row, column, cacheKey) {
if (!cacheKey || cacheKey.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTile", "constructor",
"The specified cache name is null, undefined or zero length."));
}
TextureTile.call(this, sector, level, row, column); // args are checked in the superclass' constructor
// Assign the cacheKey as the gpuCacheKey (inherited from TextureTile).
this.gpuCacheKey = cacheKey;
// Internal. Intentionally not documented.
this.textureTransform = Matrix.fromIdentity().setToUnitYFlip();
// Internal. Intentionally not documented.
this.mustClear = true;
}
FramebufferTile.prototype = Object.create(TextureTile.prototype);
/**
* Causes this tile to clear any color fragments written to its off-screen framebuffer.
* @param dc The current draw context.
*/
FramebufferTile.prototype.clearFramebuffer = function (dc) {
this.mustClear = true;
};
/**
* Causes this tile's off-screen framebuffer as the current WebGL framebuffer. WebGL operations that affect the
* framebuffer now affect this tile's framebuffer, rather than the default WebGL framebuffer.
* Color fragments are written to this tile's WebGL texture, which can be made active by calling
* [SurfaceTile.bind]{@link SurfaceTile#bind}.
*
* @param {DrawContext} dc The current draw context.
* @returns {Boolean} true if the framebuffer was bound successfully, otherwise false.
*/
FramebufferTile.prototype.bindFramebuffer = function (dc) {
var framebuffer = dc.gpuResourceCache.resourceForKey(this.gpuCacheKey);
if (!framebuffer) {
framebuffer = this.createFramebuffer(dc);
}
dc.bindFramebuffer(framebuffer);
if (this.mustClear) {
this.doClearFramebuffer(dc);
this.mustClear = false;
}
return true;
};
// Internal. Intentionally not documented.
FramebufferTile.prototype.createFramebuffer = function (dc) {
var framebuffer = new FramebufferTexture(dc.currentGlContext, this.tileWidth, this.tileHeight, false);
dc.gpuResourceCache.putResource(this.gpuCacheKey, framebuffer, framebuffer.size);
return framebuffer;
};
// Internal. Intentionally not documented.
FramebufferTile.prototype.doClearFramebuffer = function (dc) {
var gl = dc.currentGlContext;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
};
/**
* Applies the appropriate texture transform to display this tile's WebGL texture.
* @param {DrawContext} dc The current draw context.
* @param {Matrix} matrix The matrix to apply the transform to.
*/
FramebufferTile.prototype.applyInternalTransform = function (dc, matrix) {
matrix.multiplyMatrix(this.textureTransform);
};
export default FramebufferTile;

View File

@ -0,0 +1,251 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports FramebufferTileController
*/
import ArgumentError from '../error/ArgumentError';
import FramebufferTile from '../render/FramebufferTile';
import LevelSet from '../util/LevelSet';
import Location from '../geom/Location';
import Logger from '../util/Logger';
import MemoryCache from '../cache/MemoryCache';
import Sector from '../geom/Sector';
import Tile from '../util/Tile';
/**
* Constructs a framebuffer tile controller.
* @alias FramebufferTileController
* @constructor
* @classdesc Provides access to a multi-resolution WebGL framebuffer arranged as adjacent tiles in a pyramid.
* WorldWind shapes use this class internally to draw on the terrain surface. Applications typically do not
* interact with this class.
*/
function FramebufferTileController() {
/**
* The width in pixels of framebuffers associated with this controller's tiles.
* @type {Number}
* @readonly
*/
this.tileWidth = 256;
/**
* The height in pixels of framebuffers associated with this controller's tiles.
* @type {Number}
* @readonly
*/
this.tileHeight = 256;
/**
* Controls the level of detail switching for this controller. The next highest resolution level is
* used when an image's texel size is greater than this number of pixels.
* @type {Number}
* @default 1.75
*/
this.detailControl = 1.75;
// Internal. Intentionally not documented.
this.levels = new LevelSet(Sector.FULL_SPHERE, new Location(360, 360), 18, this.tileWidth, this.tileHeight);
// Internal. Intentionally not documented.
this.topLevelTiles = [];
// Internal. Intentionally not documented.
this.currentTiles = [];
// Internal. Intentionally not documented.
this.currentTimestamp = null;
// Internal. Intentionally not documented.
this.currentGlobeStateKey = null;
// Internal. Intentionally not documented.
this.tileCache = new MemoryCache(500000, 400000);
// Internal. Intentionally not documented.
this.key = "FramebufferTileController " + ++FramebufferTileController.keyPool;
}
// Internal. Intentionally not documented.
FramebufferTileController.keyPool = 0; // source of unique ids
/**
* Returns a set of multi-resolution [FramebufferTile]{@link FramebufferTile} instances appropriate for the
* current draw context that overlap a specified sector.
* @param {DrawContext} dc The current draw context.
* @param {Sector} sector The geographic region of interest.
* @returns {Array} The set of multi-resolution framebuffer tiles that overlap the sector.
* @throws {ArgumentError} If the specified sector is null.
*/
FramebufferTileController.prototype.selectTiles = function (dc, sector) {
if (!sector) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTileController",
"selectTiles", "missingSector"));
}
// Assemble a set of global tiles appropriate for the draw context.
this.assembleTiles(dc);
// Collect the tiles that overlap the specified sector and mark them as selected.
var tiles = [];
for (var i = 0, len = this.currentTiles.length; i < len; i++) {
var tile = this.currentTiles[i];
if (tile.sector.overlaps(sector)) {
tile.selected = true;
tiles.push(tile);
}
}
return tiles;
};
/**
* Draws this multi-resolution framebuffer on the terrain surface then clears the framebuffer. This has no
* effect if the framebuffer is unchanged since the last call to render.
* @param {DrawContext} dc The current draw context.
*/
FramebufferTileController.prototype.render = function (dc) {
// Exit immediately if there are no framebuffer tiles. This can happen when there ar eno surface shapes in
// the scene, for example.
if (this.currentTiles.length == 0) {
return;
}
// Collect the tiles that have changed since the last call to render.
var tiles = [];
for (var i = 0, len = this.currentTiles.length; i < len; i++) {
var tile = this.currentTiles[i];
if (tile.selected) {
tiles.push(tile);
}
}
// Draw the changed tiles on the terrain surface.
dc.surfaceTileRenderer.renderTiles(dc, tiles, 1);
// Clear the changed tile's WebGL framebuffers.
var gl = dc.currentGlContext,
framebuffer = dc.currentFramebuffer;
try {
gl.clearColor(0, 0, 0, 0);
for (i = 0, len = tiles.length; i < len; i++) {
tile = tiles[i];
tile.selected = false;
tile.bindFramebuffer(dc);
gl.clear(gl.COLOR_BUFFER_BIT);
}
} finally {
dc.bindFramebuffer(framebuffer);
}
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.assembleTiles = function (dc) {
var timestamp = dc.timestamp,
globeStateKey = dc.globeStateKey;
if (this.currentTimestamp != timestamp ||
this.currentGlobeStateKey != globeStateKey) {
this.doAssembleTiles(dc);
this.currentTimestamp = timestamp;
this.currentGlobeStateKey = globeStateKey;
}
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.doAssembleTiles = function (dc) {
this.currentTiles = [];
if (!dc.terrain) {
return;
}
if (this.topLevelTiles.length == 0) {
this.createTopLevelTiles();
}
for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
var tile = this.topLevelTiles[i];
tile.update(dc);
if (this.isTileVisible(dc, tile)) {
this.addTileOrDescendants(dc, tile);
}
}
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.createTile = function (sector, level, row, column) {
var tileKey = this.key + " " + level.levelNumber + "." + row + "." + column;
return new FramebufferTile(sector, level, row, column, tileKey);
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.createTopLevelTiles = function () {
Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles);
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.addTileOrDescendants = function (dc, tile) {
if (this.tileMeetsRenderingCriteria(dc, tile)) {
this.addTile(tile);
return;
}
var subTiles = tile.subdivideToCache(tile.level.nextLevel(), this, this.tileCache);
for (var i = 0, len = subTiles.length; i < len; i++) {
var child = subTiles[i];
child.update(dc);
if (this.isTileVisible(dc, child)) {
this.addTileOrDescendants(dc, child);
}
}
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.addTile = function (tile) {
this.currentTiles.push(tile);
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.isTileVisible = function (dc, tile) {
if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
return false;
}
if (dc.pickingMode) {
return tile.extent.intersectsFrustum(dc.pickFrustum);
}
return tile.extent.intersectsFrustum(dc.frustumInModelCoordinates);
};
// Internal. Intentionally not documented.
FramebufferTileController.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
var s = this.detailControl;
if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
s *= 1.2;
}
return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
};
export default FramebufferTileController;

View File

@ -0,0 +1,137 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ImageTile
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import TextureTile from '../render/TextureTile';
import Tile from '../util/Tile';
/**
* Constructs an image tile.
* @alias ImageTile
* @constructor
* @classdesc Represents an image applied to a portion of a globe's terrain. Applications typically do not
* interact with this class.
* @augments TextureTile
* @param {Sector} sector The sector this tile covers.
* @param {Level} level The level this tile is associated with.
* @param {Number} row This tile's row in the associated level.
* @param {Number} column This tile's column in the associated level.
* @param {String} imagePath The full path to the image.
* @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
* are less than zero, or the specified image path is null, undefined or empty.
*
*/
function ImageTile(sector, level, row, column, imagePath) {
if (!imagePath || imagePath.length < 1) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ImageTile", "constructor",
"The specified image path is null, undefined or zero length."));
}
TextureTile.call(this, sector, level, row, column); // args are checked in the superclass' constructor
/**
* This tile's image path.
* @type {String}
*/
this.imagePath = imagePath;
/**
* The tile whose texture to use when this tile's texture is not available.
* @type {Matrix}
*/
this.fallbackTile = null;
// Assign imagePath to gpuCacheKey (inherited from TextureTile).
this.gpuCacheKey = imagePath;
}
ImageTile.prototype = Object.create(TextureTile.prototype);
/**
* Returns the size of the this tile in bytes.
* @returns {Number} The size of this tile in bytes, not including the associated texture size.
*/
ImageTile.prototype.size = function () {
return this.__proto__.__proto__.size.call(this) + this.imagePath.length + 8;
};
/**
* Causes this tile's texture to be active. Implements [SurfaceTile.bind]{@link SurfaceTile#bind}.
* @param {DrawContext} dc The current draw context.
* @returns {Boolean} true if the texture was bound successfully, otherwise false.
*/
ImageTile.prototype.bind = function (dc) {
// Attempt to bind in TextureTile first.
var isBound = this.__proto__.__proto__.bind.call(this, dc);
if (isBound) {
return true;
}
if (this.fallbackTile) {
return this.fallbackTile.bind(dc);
}
return false;
};
/**
* If this tile's fallback texture is used, applies the appropriate texture transform to a specified matrix.
* @param {DrawContext} dc The current draw context.
* @param {Matrix} matrix The matrix to apply the transform to.
*/
ImageTile.prototype.applyInternalTransform = function (dc, matrix) {
if (this.fallbackTile && !dc.gpuResourceCache.resourceForKey(this.imagePath)) {
// Must apply a texture transform to map the tile's sector into its fallback's image.
this.applyFallbackTransform(matrix);
}
};
// Intentionally not documented.
ImageTile.prototype.applyFallbackTransform = function (matrix) {
var deltaLevel = this.level.levelNumber - this.fallbackTile.level.levelNumber;
if (deltaLevel <= 0)
return;
var fbTileDeltaLat = this.fallbackTile.sector.deltaLatitude(),
fbTileDeltaLon = this.fallbackTile.sector.deltaLongitude(),
sx = this.sector.deltaLongitude() / fbTileDeltaLon,
sy = this.sector.deltaLatitude() / fbTileDeltaLat,
tx = (this.sector.minLongitude - this.fallbackTile.sector.minLongitude) / fbTileDeltaLon,
ty = (this.sector.minLatitude - this.fallbackTile.sector.minLatitude) / fbTileDeltaLat;
// Apply a transform to the matrix that maps texture coordinates for this tile to texture coordinates for the
// fallback tile. Rather than perform the full set of matrix operations, a single multiply is performed with the
// precomputed non-zero values:
//
// Matrix trans = Matrix.fromTranslation(tx, ty, 0);
// Matrix scale = Matrix.fromScale(sxy, sxy, 1);
// matrix.multiply(trans);
// matrix.multiply(scale);
matrix.multiply(
sx, 0, 0, tx,
0, sy, 0, ty,
0, 0, 1, 0,
0, 0, 0, 1);
};
export default ImageTile;

View File

@ -0,0 +1,75 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports OrderedRenderable
*/
import Logger from '../util/Logger';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* Applications must not call this constructor. It is an interface class and is not meant to be instantiated
* directly.
* @alias OrderedRenderable
* @constructor
* @classdesc Represents an ordered renderable.
* This is an interface class and is not meant to be instantiated directly.
*/
function OrderedRenderable() {
/**
* This ordered renderable's display name.
* @type {String}
* @default Renderable
*/
this.displayName = "Renderable";
/**
* Indicates whether this ordered renderable is enabled.
* @type {Boolean}
* @default true
*/
this.enabled = true;
/**
* This ordered renderable's distance from the eye point in meters.
* @type {Number}
* @default Number.MAX_VALUE
*/
this.eyeDistance = Number.MAX_VALUE;
/**
* The time at which this ordered renderable was inserted into the ordered rendering list.
* @type {Number}
* @default 0
*/
this.insertionTime = 0;
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OrderedRenderable", "constructor", "abstractInvocation"));
}
/**
* Renders this ordered renderable.
* @param {DrawContext} dc The current draw context.
*/
OrderedRenderable.prototype.renderOrdered = function (dc) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OrderedRenderable", "renderOrdered", "abstractInvocation"));
};
export default OrderedRenderable;

View File

@ -0,0 +1,76 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Renderable
*/
import Logger from '../util/Logger';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* Constructs a base renderable.
* @alias Renderable
* @constructor
* @classdesc Represents a shape or other object that can be rendered. This is an abstract class and is not
* meant to be instantiated directly.
*/
function Renderable() {
/**
* The display name of the renderable.
* @type {String}
* @default "Renderable"
*/
this.displayName = "Renderable";
/**
* Indicates whether to display this renderable.
* @type {Boolean}
* @default true
*/
this.enabled = true;
/**
* Indicates the object to return as the userObject of this shape when picked. If null,
* then this shape is returned as the userObject.
* @type {Object}
* @default null
* @see [PickedObject.userObject]{@link PickedObject#userObject}
*/
this.pickDelegate = null;
/**
* An application defined object associated with this renderable. A typical use case is to associate
* application defined data with a picked renderable.
* @type {Object}
* @default An empty object
*/
this.userProperties = {};
}
/**
* Render this renderable. Some shapes actually draw themselves during this call, others only add themselves
* to the draw context's ordered rendering list for subsequent drawing when their renderOrdered method is called.
* This method is intended to be called by layers such as {@link RenderableLayer} and not by applications.
* @param {DrawContext} dc The current draw context.
*/
Renderable.prototype.render = function (dc) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "Renderable", "render", "abstractInvocation"));
};
export default Renderable;

View File

@ -0,0 +1,128 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports ScreenCreditController
*/
import ArgumentError from '../error/ArgumentError';
import Color from '../util/Color';
import Font from '../util/Font';
import Layer from '../layer/Layer';
import Logger from '../util/Logger';
import Offset from '../util/Offset';
import ScreenText from '../shapes/ScreenText';
/**
* Constructs a screen credit controller.
* @alias ScreenCreditController
* @constructor
* @augments Layer
* @classdesc Collects and displays screen credits.
*/
function ScreenCreditController() {
Layer.call(this, "ScreenCreditController");
/**
* An {@link Offset} indicating where to place the attributions on the screen.
* @type {Offset}
* @default The lower left corner of the window with an 11px left margin and a 2px bottom margin.
*/
this.creditPlacement = new Offset(WorldWind.OFFSET_PIXELS, 11, WorldWind.OFFSET_PIXELS, 2);
/**
* The amount of horizontal spacing between adjacent attributions.
* @type {number}
* @default An 11px margin between attributions.
*/
this.creditMargin = 11;
// Apply 50% opacity to all shapes rendered by this layer.
this.opacity = 0.5;
// Internal. Intentionally not documented.
this.credits = [];
}
ScreenCreditController.prototype = Object.create(Layer.prototype);
/**
* Clears all credits from this controller.
*/
ScreenCreditController.prototype.clear = function () {
this.credits = [];
};
/**
* Adds a credit to this controller.
* @param {String} creditString The text to display in the credits area.
* @param {Color} color The color with which to draw the string.
* @param {String} hyperlinkUrl Optional argument if screen credit is intended to work as a hyperlink.
* @throws {ArgumentError} If either the specified string or color is null or undefined.
*/
ScreenCreditController.prototype.addCredit = function (creditString, color, hyperlinkUrl) {
if (!creditString) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenCreditController", "addCredit", "missingText"));
}
if (!color) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenCreditController", "addCredit", "missingColor"));
}
// Verify if text credit is not already in controller, if it is, don't add it.
for (var i = 0, len = this.credits.length; i < len; i++) {
if (this.credits[i].text === creditString) {
return;
}
}
var credit = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), creditString);
credit.attributes.font = new Font(10);
credit.attributes.color = color;
credit.attributes.enableOutline = false;
credit.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0);
// Append new user property to store URL for hyperlinking.
// (See BasicWorldWindowController.handleClickOrTap).
if (hyperlinkUrl) {
credit.userProperties.url = hyperlinkUrl;
}
this.credits.push(credit);
};
// Internal use only. Intentionally not documented.
ScreenCreditController.prototype.doRender = function (dc) {
var point = this.creditPlacement.offsetForSize(dc.viewport.width, dc.viewport.height);
for (var i = 0, len = this.credits.length; i < len; i++) {
// Place the credit text on screen and render it.
this.credits[i].screenOffset.x = point[0];
this.credits[i].screenOffset.y = point[1];
this.credits[i].render(dc);
// Advance the screen position for the next credit.
dc.textRenderer.typeFace = this.credits[i].attributes.font;
dc.textRenderer.outlineWidth = this.credits[i].attributes.outlineWidth;
dc.textRenderer.enableOutline = this.credits[i].attributes.enableOutline;
point[0] += dc.textRenderer.textSize(this.credits[i].text)[0];
point[0] += this.creditMargin;
}
};
export default ScreenCreditController;

View File

@ -0,0 +1,61 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports SurfaceRenderable
*/
import Logger from '../util/Logger';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* Applications must not call this constructor. It is an interface class and is not meant to be instantiated
* directly.
* @alias SurfaceRenderable
* @constructor
* @classdesc Represents a surface renderable.
* This is an interface class and is not meant to be instantiated directly.
*/
function SurfaceRenderable() {
/**
* This surface renderable's display name.
* @type {String}
* @default Renderable
*/
this.displayName = "Renderable";
/**
* Indicates whether this surface renderable is enabled.
* @type {Boolean}
* @default true
*/
this.enabled = true;
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRenderable", "constructor", "abstractInvocation"));
}
/**
* Renders this surface renderable.
* @param {DrawContext} dc The current draw context.
*/
SurfaceRenderable.prototype.renderSurface = function (dc) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRenderable", "renderSurface", "abstractInvocation"));
};
export default SurfaceRenderable;

View File

@ -0,0 +1,72 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports SurfaceTile
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import Sector from '../geom/Sector';
import UnsupportedOperationError from '../error/UnsupportedOperationError';
/**
* Constructs a surface tile for a specified sector.
* @alias SurfaceTile
* @constructor
* @classdesc Defines an abstract base class for imagery to be rendered on terrain. Applications typically
* do not interact with this class.
* @param {Sector} sector The sector of this surface tile.
* @throws {ArgumentError} If the specified sector is null or undefined.
*/
function SurfaceTile(sector) {
if (!sector) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "constructor",
"missingSector"));
}
/**
* The sector spanned by this surface tile.
* @type {Sector}
*/
this.sector = sector;
}
/**
* Causes this surface tile to be active, typically by binding the tile's texture in WebGL.
* Subclasses must override this function.
* @param {DrawContext} dc The current draw context.
* @returns {Boolean} true if the resource was successfully bound, otherwise false.
*/
SurfaceTile.prototype.bind = function (dc) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "bind", "abstractInvocation"));
};
/**
* Applies this surface tile's internal transform, typically a texture transform to align the associated
* resource with the terrain.
* Subclasses must override this function.
* @param {DrawContext} dc The current draw context.
* @param {Matrix} matrix The transform to apply.
*/
SurfaceTile.prototype.applyInternalTransform = function (dc, matrix) {
throw new UnsupportedOperationError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "applyInternalTransform", "abstractInvocation"));
};
export default SurfaceTile;

View File

@ -0,0 +1,189 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports SurfaceTileRenderer
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import SurfaceShapeTile from '../shapes/SurfaceShapeTile';
import SurfaceTileRendererProgram from '../shaders/SurfaceTileRendererProgram';
/**
* Constructs a new surface tile renderer.
* @alias SurfaceTileRenderer
* @constructor
* @classdesc This class is responsible for rendering imagery onto the terrain.
* It is meant to be used internally. Applications typically do not interact with this class.
*/
function SurfaceTileRenderer() {
// Scratch values to avoid constantly recreating these matrices.
this.texMaskMatrix = Matrix.fromIdentity();
this.texSamplerMatrix = Matrix.fromIdentity();
// Internal. Intentionally not documented.
this.isSurfaceShapeTileRendering = false;
}
/**
* Render a specified collection of surface tiles.
* @param {DrawContext} dc The current draw context.
* @param {SurfaceTile[]} surfaceTiles The surface tiles to render.
* @param {Number} opacity The opacity at which to draw the surface tiles.
* @param {Boolean} tilesHaveOpacity If true, incoming tiles each have their own opacity property and
* it's value is applied when the tile is drawn.
* @throws {ArgumentError} If the specified surface tiles array is null or undefined.
*/
SurfaceTileRenderer.prototype.renderTiles = function (dc, surfaceTiles, opacity, tilesHaveOpacity) {
if (!surfaceTiles) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRenderer", "renderTiles",
"Specified surface tiles array is null or undefined."));
}
if (surfaceTiles.length < 1)
return;
var terrain = dc.terrain,
gl = dc.currentGlContext,
tileCount = 0,// for frame statistics,
program,
terrainTile,
terrainTileSector,
surfaceTile,
currentTileOpacity = 1;
if (!terrain)
return;
this.isSurfaceShapeTileRendering = surfaceTiles[0] instanceof SurfaceShapeTile;
opacity *= dc.surfaceOpacity;
// For each terrain tile, render it for each overlapping surface tile.
program = this.beginRendering(dc, opacity);
terrain.beginRendering(dc);
try {
for (var i = 0, ttLen = terrain.surfaceGeometry.length; i < ttLen; i++) {
terrainTile = terrain.surfaceGeometry[i];
terrainTileSector = terrainTile.sector;
terrain.beginRenderingTile(dc, terrainTile);
try {
// Render the terrain tile for each overlapping surface tile.
for (var j = 0, stLen = surfaceTiles.length; j < stLen; j++) {
surfaceTile = surfaceTiles[j];
if (surfaceTile.sector.overlaps(terrainTileSector)) {
if (surfaceTile.bind(dc)) {
if (dc.pickingMode) {
if (surfaceTile.pickColor) {
program.loadColor(gl, surfaceTile.pickColor);
} else {
// Surface shape tiles don't use a pick color. Pick colors are encoded into
// the colors of the individual shapes drawn into the tile.
}
} else {
if (tilesHaveOpacity && surfaceTile.opacity != currentTileOpacity) {
program.loadOpacity(gl, opacity * surfaceTile.opacity);
currentTileOpacity = surfaceTile.opacity;
}
}
this.applyTileState(dc, terrainTile, surfaceTile);
terrain.renderTile(dc, terrainTile);
++tileCount;
}
}
}
}
catch (e) {
console.log(e);
}
finally {
terrain.endRenderingTile(dc, terrainTile);
}
}
}
catch (e) {
console.log(e);
}
finally {
terrain.endRendering(dc);
this.endRendering(dc);
dc.frameStatistics.incrementRenderedTileCount(tileCount);
}
};
// Intentionally not documented.
SurfaceTileRenderer.prototype.beginRendering = function (dc, opacity) {
var gl = dc.currentGlContext,
program = dc.findAndBindProgram(SurfaceTileRendererProgram);
program.loadTexSampler(gl, gl.TEXTURE0);
if (dc.pickingMode && !this.isSurfaceShapeTileRendering) {
program.loadModulateColor(gl, true);
} else {
program.loadModulateColor(gl, false);
program.loadOpacity(gl, opacity);
}
return program;
};
// Intentionally not documented.
SurfaceTileRenderer.prototype.endRendering = function (dc) {
var gl = dc.currentGlContext;
gl.bindTexture(gl.TEXTURE_2D, null);
};
// Intentionally not documented.
SurfaceTileRenderer.prototype.applyTileState = function (dc, terrainTile, surfaceTile) {
// Sets up the texture transform and mask that applies the texture tile to the terrain tile.
var gl = dc.currentGlContext,
program = dc.currentProgram,
terrainSector = terrainTile.sector,
terrainDeltaLat = terrainSector.deltaLatitude(),
terrainDeltaLon = terrainSector.deltaLongitude(),
surfaceSector = surfaceTile.sector,
rawSurfaceDeltaLat = surfaceSector.deltaLatitude(),
rawSurfaceDeltaLon = surfaceSector.deltaLongitude(),
surfaceDeltaLat = rawSurfaceDeltaLat > 0 ? rawSurfaceDeltaLat : 1,
surfaceDeltaLon = rawSurfaceDeltaLon > 0 ? rawSurfaceDeltaLon : 1,
sScale = terrainDeltaLon / surfaceDeltaLon,
tScale = terrainDeltaLat / surfaceDeltaLat,
sTrans = -(surfaceSector.minLongitude - terrainSector.minLongitude) / surfaceDeltaLon,
tTrans = -(surfaceSector.minLatitude - terrainSector.minLatitude) / surfaceDeltaLat;
this.texMaskMatrix.set(
sScale, 0, 0, sTrans,
0, tScale, 0, tTrans,
0, 0, 1, 0,
0, 0, 0, 1
);
this.texSamplerMatrix.setToUnitYFlip();
surfaceTile.applyInternalTransform(dc, this.texSamplerMatrix);
this.texSamplerMatrix.multiplyMatrix(this.texMaskMatrix);
program.loadTexSamplerMatrix(gl, this.texSamplerMatrix);
program.loadTexMaskMatrix(gl, this.texMaskMatrix);
};
export default SurfaceTileRenderer;

View File

@ -0,0 +1,307 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TextRenderer
*/
import ArgumentError from '../error/ArgumentError';
import BasicTextureProgram from '../shaders/BasicTextureProgram';
import Color from '../util/Color';
import Font from '../util/Font';
import Logger from '../util/Logger';
import Matrix from '../geom/Matrix';
import Texture from '../render/Texture';
import Vec2 from '../geom/Vec2';
/**
* Constructs a TextRenderer instance.
* @alias TextRenderer
* @constructor
* @classdesc Provides methods useful for displaying text. An instance of this class is attached to the
* WorldWindow {@link DrawContext} and is not intended to be used independently of that. Applications typically do
* not create instances of this class.
* @param {drawContext} drawContext The current draw context. Typically the same draw context that TextRenderer
* is attached to.
* @throws {ArgumentError} If the specified draw context is null or undefined.
*/
function TextRenderer(drawContext) {
if (!drawContext) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "TextRenderer", "constructor",
"missingDc"));
}
// Internal use only. Intentionally not documented.
this.canvas2D = document.createElement("canvas");
// Internal use only. Intentionally not documented.
this.ctx2D = this.canvas2D.getContext("2d");
// Internal use only. Intentionally not documented.
this.dc = drawContext;
/**
* Indicates if the text will feature an outline around its characters.
* @type {boolean}
*/
this.enableOutline = true;
// Internal use only. Intentionally not documented.
this.lineSpacing = 0.15; // fraction of font size
/**
* The color for the Text outline.
* Its default has half transparency to avoid visual artifacts that appear while fully opaque.
* @type {Color}
*/
this.outlineColor = new Color(0, 0, 0, 0.5);
/**
* Indicates the text outline width (or thickness) in pixels.
* @type {number}
*/
this.outlineWidth = 4;
/**
* The text color.
* @type {Color}
*/
this.textColor = new Color(1, 1, 1, 1);
/**
* The text size, face and other characteristics, as described in [Font]{@link Font}.
* @type {Font}
*/
this.typeFace = new Font(14);
}
/**
* Returns the width and height of a specified text string considering the current typeFace and outline usage.
* @param {string} text The text string.
* @returns {Vec2} A vector indicating the text's width and height, respectively, in pixels.
*/
TextRenderer.prototype.textSize = function (text) {
if (text.length === 0) {
return new Vec2(0, 0);
}
this.ctx2D.font = this.typeFace.fontString;
var lines = text.split("\n"),
height = lines.length * (this.typeFace.size * (1 + this.lineSpacing)),
maxWidth = 0;
for (var i = 0; i < lines.length; i++) {
maxWidth = Math.max(maxWidth, this.ctx2D.measureText(lines[i]).width);
}
if (this.enableOutline) {
maxWidth += this.outlineWidth;
height += this.outlineWidth;
}
return new Vec2(maxWidth, height);
};
/**
* Creates a texture for a specified text string and current TextRenderer state.
* @param {String} text The text string.
* @returns {Texture} A texture for the specified text string.
*/
TextRenderer.prototype.renderText = function (text) {
if (text && text.length > 0) {
var canvas2D = this.drawText(text);
return new Texture(this.dc.currentGlContext, canvas2D);
} else {
return null;
}
};
/**
* Creates a 2D Canvas for a specified text string while considering current TextRenderer state in
* regards to outline usage and color, text color, typeface, and outline width.
* @param {String} text The text string.
* @returns {canvas2D} A 2D Canvas for the specified text string.
*/
TextRenderer.prototype.drawText = function (text) {
var ctx2D = this.ctx2D,
canvas2D = this.canvas2D,
textSize = this.textSize(text),
lines = text.split("\n"),
strokeOffset = this.enableOutline ? this.outlineWidth / 2 : 0,
pixelScale = this.dc.pixelScale;
canvas2D.width = Math.ceil(textSize[0]) * pixelScale;
canvas2D.height = Math.ceil(textSize[1]) * pixelScale;
ctx2D.scale(pixelScale, pixelScale);
ctx2D.font = this.typeFace.fontString;
ctx2D.textBaseline = "bottom";
ctx2D.textAlign = this.typeFace.horizontalAlignment;
ctx2D.fillStyle = this.textColor.toCssColorString();
ctx2D.strokeStyle = this.outlineColor.toCssColorString();
ctx2D.lineWidth = this.outlineWidth;
ctx2D.lineCap = "round";
ctx2D.lineJoin = "round";
if (this.typeFace.horizontalAlignment === "left") {
ctx2D.translate(strokeOffset, 0);
} else if (this.typeFace.horizontalAlignment === "right") {
ctx2D.translate(textSize[0] - strokeOffset, 0);
} else {
ctx2D.translate(textSize[0] / 2, 0);
}
for (var i = 0; i < lines.length; i++) {
ctx2D.translate(0, this.typeFace.size * (1 + this.lineSpacing) + strokeOffset);
if (this.enableOutline) {
ctx2D.strokeText(lines[i], 0, 0);
}
ctx2D.fillText(lines[i], 0, 0);
}
return canvas2D;
};
/**
* Calculates maximum line height based on the current typeFace and outline usage of TextRenderer.
* @returns {Vec2} A vector indicating the text's width and height, respectively, in pixels.
*/
TextRenderer.prototype.getMaxLineHeight = function () {
// Check underscore + capital E with acute accent
return this.textSize("_\u00c9")[1];
};
/**
* Wraps the text based on width and height using new line delimiter
* @param {String} text The text to wrap.
* @param {Number} width The width in pixels.
* @param {Number} height The height in pixels.
* @returns {String} The wrapped text.
*/
TextRenderer.prototype.wrap = function (text, width, height) {
if (!text) {
throw new ArgumentError(
Logger.logMessage(Logger.WARNING, "TextRenderer", "wrap", "missing text"));
}
var i;
var lines = text.split("\n");
var wrappedText = "";
// Wrap each line
for (i = 0; i < lines.length; i++) {
lines[i] = this.wrapLine(lines[i], width);
}
// Concatenate all lines in one string with new line separators
// between lines - not at the end
// Checks for height limit.
var currentHeight = 0;
var heightExceeded = false;
var maxLineHeight = this.getMaxLineHeight();
for (i = 0; i < lines.length && !heightExceeded; i++) {
var subLines = lines[i].split("\n");
for (var j = 0; j < subLines.length && !heightExceeded; j++) {
if (height <= 0 || currentHeight + maxLineHeight <= height) {
wrappedText += subLines[j];
currentHeight += maxLineHeight + this.lineSpacing;
if (j < subLines.length - 1) {
wrappedText += '\n';
}
}
else {
heightExceeded = true;
}
}
if (i < lines.length - 1 && !heightExceeded) {
wrappedText += '\n';
}
}
// Add continuation string if text truncated
if (heightExceeded) {
if (wrappedText.length > 0) {
wrappedText = wrappedText.substring(0, wrappedText.length - 1);
}
wrappedText += "...";
}
return wrappedText;
};
/**
* Wraps a line of text based on width and height
* @param {String} text The text to wrap.
* @param {Number} width The width in pixels.
* @returns {String} The wrapped text.
*/
TextRenderer.prototype.wrapLine = function (text, width) {
var wrappedText = "";
// Single line - trim leading and trailing spaces
var source = text.trim();
var lineBounds = this.textSize(source);
if (lineBounds[0] > width) {
// Split single line to fit preferred width
var line = "";
var start = 0;
var end = source.indexOf(' ', start + 1);
while (start < source.length) {
if (end === -1) {
end = source.length; // last word
}
// Extract a 'word' which is in fact a space and a word
var word = source.substring(start, end);
var linePlusWord = line + word;
if (this.textSize(linePlusWord)[0] <= width) {
// Keep adding to the current line
line += word;
}
else {
// Width exceeded
if (line.length !== 0) {
// Finish current line and start new one
wrappedText += line;
wrappedText += '\n';
line = "";
line += word.trim(); // get read of leading space(s)
}
else {
// Line is empty, force at least one word
line += word.trim();
}
}
// Move forward in source string
start = end;
if (start < source.length - 1) {
end = source.indexOf(' ', start + 1);
}
}
// Gather last line
wrappedText += line;
}
else {
// Line doesn't need to be wrapped
wrappedText += source;
}
return wrappedText;
};
export default TextRenderer;

View File

@ -0,0 +1,187 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports Texture
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import WWMath from '../util/WWMath';
/**
* Constructs a texture for a specified image.
* @alias Texture
* @constructor
* @classdesc Represents a WebGL texture. Applications typically do not interact with this class.
* @param {WebGLRenderingContext} gl The current WebGL rendering context.
* @param {Image} image The texture's image.
* @param {GLenum} wrapMode Optional. Specifies the wrap mode of the texture. Defaults to gl.CLAMP_TO_EDGE
* @throws {ArgumentError} If the specified WebGL context or image is null or undefined.
*/
function Texture(gl, image, wrapMode) {
if (!gl) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
"missingGlContext"));
}
if (!image) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
"missingImage"));
}
if (!wrapMode) {
wrapMode = gl.CLAMP_TO_EDGE;
}
var textureId = gl.createTexture(),
isPowerOfTwo = WWMath.isPowerOfTwo(image.width) && WWMath.isPowerOfTwo(image.height);
this.originalImageWidth = image.width;
this.originalImageHeight = image.height;
if (wrapMode === gl.REPEAT && !isPowerOfTwo) {
image = this.resizeImage(image);
isPowerOfTwo = true;
}
this.imageWidth = image.width;
this.imageHeight = image.height;
this.size = image.width * image.height * 4;
gl.bindTexture(gl.TEXTURE_2D, textureId);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
isPowerOfTwo ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
gl.texImage2D(gl.TEXTURE_2D, 0,
gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
if (isPowerOfTwo) {
gl.generateMipmap(gl.TEXTURE_2D);
}
this.textureId = textureId;
/**
* The time at which this texture was created.
* @type {Date}
*/
this.creationTime = new Date();
// Internal use only. Intentionally not documented.
this.texParameters = {};
// Internal use only. Intentionally not documented.
// https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotrop
this.anisotropicFilterExt = gl.getExtension("EXT_texture_filter_anisotropic") ||
gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
}
/**
* Sets a texture parameter to apply when binding this texture.
*
* Currently only gl.TEXTURE_MAG_FILTER has an effect.
*
* @param {Glenum} name The name of the parameter
* @param {GLint} value The value for this parameter
*/
Texture.prototype.setTexParameter = function (name, value) {
this.texParameters[name] = value;
};
/**
* Returns the value of a texture parameter to be assigned to this texture.
* @param {Glenum} name The name of the parameter
* @returns {GLint} The value for this parameter
*/
Texture.prototype.getTexParameter = function (name) {
return this.texParameters[name];
};
/**
* Clears the list of texture parameters to apply when binding this texture.
*/
Texture.prototype.clearTexParameters = function () {
this.texParameters = {};
};
/**
* Disposes of the WebGL texture object associated with this texture.
* @param gl
*/
Texture.prototype.dispose = function (gl) {
gl.deleteTexture(this.textureId);
delete this.textureId;
};
/**
* Binds this texture in the current WebGL graphics context.
* @param {DrawContext} dc The current draw context.
*/
Texture.prototype.bind = function (dc) {
var gl = dc.currentGlContext;
gl.bindTexture(gl.TEXTURE_2D, this.textureId);
this.applyTexParameters(dc);
dc.frameStatistics.incrementTextureLoadCount(1);
return true;
};
/**
* Applies the configured texture parameters to the OpenGL context.
* @param {DrawContext} dc The current draw context.
*/
Texture.prototype.applyTexParameters = function (dc) {
var gl = dc.currentGlContext;
// Configure the OpenGL texture magnification function. Use linear by default.
var textureMagFilter = this.texParameters[gl.TEXTURE_MAG_FILTER] || gl.LINEAR;
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, textureMagFilter);
// Try to enable the anisotropic texture filtering only if we have a linear magnification filter.
// This can't be enabled all the time because Windows seems to ignore the TEXTURE_MAG_FILTER parameter when
// this extension is enabled.
if (textureMagFilter === gl.LINEAR) {
// Setup 4x anisotropic texture filtering when this feature is available.
if (this.anisotropicFilterExt) {
gl.texParameteri(gl.TEXTURE_2D, this.anisotropicFilterExt.TEXTURE_MAX_ANISOTROPY_EXT, 4);
}
}
};
/**
* Resizes an image to a power of two.
* @param {Image} image The image to resize.
*/
Texture.prototype.resizeImage = function (image) {
var canvas = document.createElement("canvas");
canvas.width = WWMath.powerOfTwoFloor(image.width);
canvas.height = WWMath.powerOfTwoFloor(image.height);
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
return canvas;
};
export default Texture;

View File

@ -0,0 +1,84 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports TextureTile
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Tile from '../util/Tile';
/**
* Constructs a texture tile.
* @alias TextureTile
* @constructor
* @augments Tile
* @classdesc Represents an image applied to a portion of a globe's terrain. Applications typically do not
* interact with this class.
* @param {Sector} sector The sector this tile covers.
* @param {Level} level The level this tile is associated with.
* @param {Number} row This tile's row in the associated level.
* @param {Number} column This tile's column in the associated level.
* @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
* are less than zero, or the specified image path is null, undefined or empty.
*
*/
function TextureTile(sector, level, row, column) {
Tile.call(this, sector, level, row, column); // args are checked in the superclass' constructor
/**
* GPU cache key
* @type {string}
*/
this.gpuCacheKey = null;
}
TextureTile.prototype = Object.create(Tile.prototype);
/**
* Returns the size of the this tile in bytes.
* @returns {Number} The size of this tile in bytes, not including the associated texture size.
*/
TextureTile.prototype.size = function () {
return Tile.prototype.size.call(this);
};
/**
* Causes this tile's texture to be active. Implements [SurfaceTile.bind]{@link SurfaceTile#bind}.
* @param {DrawContext} dc The current draw context.
* @returns {Boolean} true if the texture was bound successfully, otherwise false.
*/
TextureTile.prototype.bind = function (dc) {
var texture = dc.gpuResourceCache.resourceForKey(this.gpuCacheKey);
if (texture) {
return texture.bind(dc);
}
return false;
};
/**
* If this tile's fallback texture is used, applies the appropriate texture transform to a specified matrix.
* Otherwise, this is a no-op.
* @param {DrawContext} dc The current draw context.
* @param {Matrix} matrix The matrix to apply the transform to.
*/
TextureTile.prototype.applyInternalTransform = function (dc, matrix) {
// Override this method if the tile has a fallback texture.
};
export default TextureTile;

View File

@ -0,0 +1,320 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports AtmosphereProgram
*/
import ArgumentError from '../error/ArgumentError';
import GpuProgram from '../shaders/GpuProgram';
import Logger from '../util/Logger';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
* This method then compiles the shaders and then links the program if compilation is successful.
*
* @alias AtmosphereProgram
* @constructor
* @augments GpuProgram
* @classdesc AtmosphereProgram is a GLSL program that draws the atmosphere.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
function AtmosphereProgram(gl, vertexShaderSource, fragmentShaderSource, attribute) {
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, attribute);
// Frag color mode indicates the atmospheric scattering color components written to the fragment color.
this.FRAGMODE_SKY = 1;
this.FRAGMODE_GROUND_PRIMARY = 2;
this.FRAGMODE_GROUND_SECONDARY = 3;
this.FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;
/**
* The globe's atmosphere altitude.
* @type {Number}
* @default 160000.0 meters
*/
this.altitude = 160000;
/**
* This atmosphere's Rayleigh scale depth.
* @type {Number}
* @default 0.25
*/
this.rayleighScaleDepth = 0.25;
/**
* The WebGL location for this program's 'fragMode' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.fragModeLocation = this.uniformLocation(gl, "fragMode");
/**
* The WebGL location for this program's 'mvpMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");
/**
* The WebGL location for this program's 'texCoordMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.texCoordMatrixLocation = this.uniformLocation(gl, "texCoordMatrix");
/**
* The WebGL location for this program's 'vertexOrigin' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.vertexOriginLocation = this.uniformLocation(gl, "vertexOrigin");
/**
* The WebGL location for this program's 'eyePoint' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.eyePointLocation = this.uniformLocation(gl, "eyePoint");
/**
* The WebGL location for this program's 'eyeMagnitude' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.eyeMagnitudeLocation = this.uniformLocation(gl, "eyeMagnitude");
/**
* The WebGL location for this program's 'eyeMagnitude2' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.eyeMagnitude2Location = this.uniformLocation(gl, "eyeMagnitude2");
/**
* The WebGL location for this program's 'lightDirection' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.lightDirectionLocation = this.uniformLocation(gl, "lightDirection");
/**
* The WebGL location for this program's 'atmosphereRadius' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.atmosphereRadiusLocation = this.uniformLocation(gl, "atmosphereRadius");
/**
* The WebGL location for this program's 'atmosphereRadius2' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.atmosphereRadius2Location = this.uniformLocation(gl, "atmosphereRadius2");
/**
* The WebGL location for this program's 'globeRadius' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.globeRadiusLocation = this.uniformLocation(gl, "globeRadius");
/**
* The WebGL location for this program's 'scale' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.scaleLocation = this.uniformLocation(gl, "scale");
/**
* The WebGL location for this program's 'scaleDepth' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.scaleDepthLocation = this.uniformLocation(gl, "scaleDepth");
/**
* The WebGL location for this program's 'scaleOverScaleDepth' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.scaleOverScaleDepthLocation = this.uniformLocation(gl, "scaleOverScaleDepth");
this.scratchArray9 = new Float32Array(9);
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
AtmosphereProgram.key = "WorldWindGpuAtmosphereProgram";
// Inherit from GpuProgram.
AtmosphereProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Returns the atmosphere's altitude.
* @returns {Number} The atmosphere's altitude in meters.
*/
AtmosphereProgram.prototype.getAltitude = function () {
return this.altitude;
};
/**
* Loads the specified number as the value of this program's 'fragMode' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} fragMode The frag mode value.
* @throws {ArgumentError} If the specified number is null or undefined.
*/
AtmosphereProgram.prototype.loadFragMode = function (gl, fragMode) {
if (!fragMode) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadFragMode", "missingFragMode"));
}
gl.uniform1i(this.fragModeLocation, fragMode);
};
/**
* Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
AtmosphereProgram.prototype.loadModelviewProjection = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadModelviewProjection",
"missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
};
/**
* Loads the specified vector as the value of this program's 'vertexOrigin' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Vec3} vector The vector to load.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
AtmosphereProgram.prototype.loadVertexOrigin = function (gl, vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadVertexOrigin", "missingVector"));
}
gl.uniform3f(this.vertexOriginLocation, vector[0], vector[1], vector[2]);
};
/**
* Loads the specified vector as the value of this program's 'lightDirection' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Vec3} vector The vector to load.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
AtmosphereProgram.prototype.loadLightDirection = function (gl, vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadLightDirection", "missingVector"));
}
gl.uniform3f(this.lightDirectionLocation, vector[0], vector[1], vector[2]);
};
/**
* Loads the specified vector as the value of this program's 'lightDirection' uniform variable,
* the magnitude's specified vector as the value of this program's 'eyeMagnitude' uniform variable and
* the squared magnitude's specified vector as the value of this program's 'eyeMagnitude2' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Vec3} vector The vector to load.
* @throws {ArgumentError} If the specified vector is null or undefined.
*/
AtmosphereProgram.prototype.loadEyePoint = function (gl, vector) {
if (!vector) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadEyePoint", "missingVector"));
}
gl.uniform3f(this.eyePointLocation, vector[0], vector[1], vector[2]);
gl.uniform1f(this.eyeMagnitudeLocation, vector.magnitude());
gl.uniform1f(this.eyeMagnitude2Location, vector.magnitudeSquared());
};
/**
* Loads the specified number as the value of this program's 'globeRadius' uniform variable and the specified
* number which add the altitude value as the value of this program's 'atmosphereRadius' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} globeRadius The globe radius value.
* @throws {ArgumentError} If the specified number is null or undefined.
*/
AtmosphereProgram.prototype.loadGlobeRadius = function (gl, globeRadius) {
if (!globeRadius) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadGlobeRadius",
"missingGlobeRadius"));
}
var gr = globeRadius;
var ar = gr + this.altitude;
gl.uniform1f(this.globeRadiusLocation, gr);
gl.uniform1f(this.atmosphereRadiusLocation, ar);
gl.uniform1f(this.atmosphereRadius2Location, ar * ar);
};
/**
* Sets the program's 'scale', 'scaleDepth' and 'scaleOverScaleDepth' uniform variables.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
*/
AtmosphereProgram.prototype.setScale = function (gl) {
gl.uniform1f(this.scaleLocation, 1 / this.getAltitude());
gl.uniform1f(this.scaleDepthLocation, this.rayleighScaleDepth);
gl.uniform1f(this.scaleOverScaleDepthLocation, 1 / this.getAltitude() / this.rayleighScaleDepth);
};
/**
* Loads the specified matrix as the value of this program's 'texCoordMatrix' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix3} matrix The texture coordinate matrix.
*/
AtmosphereProgram.prototype.loadTexMatrix = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadTexMatrix",
"missingMatrix"));
}
matrix.columnMajorComponents(this.scratchArray9);
gl.uniformMatrix3fv(this.texCoordMatrixLocation, false, this.scratchArray9);
};
export default AtmosphereProgram;

View File

@ -0,0 +1,127 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports BasicProgram
*/
import ArgumentError from '../error/ArgumentError';
import Color from '../util/Color';
import GpuProgram from '../shaders/GpuProgram';
import Logger from '../util/Logger';
import BasicVertex from './glsl/basic_vertex.glsl';
import BasicFragment from './glsl/basic_fragment.glsl';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
* method then compiles the shaders and then links the program if compilation is successful. Use the bind method to make the
* program current during rendering.
*
* @alias BasicProgram
* @constructor
* @augments GpuProgram
* @classdesc BasicProgram is a GLSL program that draws geometry in a solid color.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
function BasicProgram(gl) {
var vertexShaderSource = BasicVertex,
fragmentShaderSource = BasicFragment;
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource);
/**
* The WebGL location for this program's 'vertexPoint' attribute.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");
/**
* The WebGL location for this program's 'mvpMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");
/**
* The WebGL location for this program's 'color' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.colorLocation = this.uniformLocation(gl, "color");
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
BasicProgram.key = "WorldWindGpuBasicProgram";
// Inherit from GpuProgram.
BasicProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
BasicProgram.prototype.loadModelviewProjection = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BasicProgram", "loadModelviewProjection", "missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
};
/**
* Loads the specified color as the value of this program's 'color' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Color} color The color to load.
* @throws {ArgumentError} If the specified color is null or undefined.
*/
BasicProgram.prototype.loadColor = function (gl, color) {
if (!color) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BasicProgram", "loadColor", "missingColor"));
}
this.loadUniformColor(gl, color, this.colorLocation);
};
/**
* Loads the specified RGBA color components as the value of this program's 'color' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} red The red component, a number between 0 and 1.
* @param {Number} green The green component, a number between 0 and 1.
* @param {Number} blue The blue component, a number between 0 and 1.
* @param {Number} alpha The alpha component, a number between 0 and 1.
*/
BasicProgram.prototype.loadColorComponents = function (gl, red, green, blue, alpha) {
this.loadUniformColorComponents(gl, red, green, blue, alpha, this.colorLocation);
};
export default BasicProgram;

View File

@ -0,0 +1,256 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports BasicTextureProgram
*/
import ArgumentError from '../error/ArgumentError';
import Color from '../util/Color';
import GpuProgram from '../shaders/GpuProgram';
import Logger from '../util/Logger';
import BasicTextureVertex from './glsl/basic_texture_vertex.glsl';
import BasicTextureFragment from './glsl/basic_texture_fragment.glsl';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
* method then compiles the shaders and then links the program if compilation is successful. Use the bind method to make the
* program current during rendering.
*
* @alias BasicTextureProgram
* @constructor
* @augments GpuProgram
* @classdesc BasicTextureProgram is a GLSL program that draws textured or untextured geometry.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or if linking of
* the compiled shaders into a program fails.
*/
function BasicTextureProgram(gl) {
var vertexShaderSource = BasicTextureVertex,
fragmentShaderSource = BasicTextureFragment;
// Specify bindings to avoid the WebGL performance warning that's generated when normalVector gets
// bound to location 0.
var bindings = ["vertexPoint", "normalVector", "vertexTexCoord"];
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, bindings);
/**
* The WebGL location for this program's 'vertexPoint' attribute.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");
/**
* The WebGL location for this program's 'normalVector' attribute.
* @type {Number}
* @readonly
*/
this.normalVectorLocation = this.attributeLocation(gl, "normalVector");
/**
* The WebGL location for this program's 'vertexTexCoord' attribute.
* @type {Number}
* @readonly
*/
this.vertexTexCoordLocation = this.attributeLocation(gl, "vertexTexCoord");
/**
* The WebGL location for this program's 'mvpMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");
/**
* The WebGL location for this program's 'mvInverseMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvInverseMatrixLocation = this.uniformLocation(gl, "mvInverseMatrix");
/**
* The WebGL location for this program's 'color' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.colorLocation = this.uniformLocation(gl, "color");
/**
* The WebGL location for this program's 'enableTexture' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.textureEnabledLocation = this.uniformLocation(gl, "enableTexture");
/**
* The WebGL location for this program's 'modulateColor' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.modulateColorLocation = this.uniformLocation(gl, "modulateColor");
/**
* The WebGL location for this program's 'textureSampler' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.textureUnitLocation = this.uniformLocation(gl, "textureSampler");
/**
* The WebGL location for this program's 'texCoordMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.textureMatrixLocation = this.uniformLocation(gl, "texCoordMatrix");
/**
* The WebGL location for this program's 'opacity' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.opacityLocation = this.uniformLocation(gl, "opacity");
/**
* The WegGL location for this program's 'enableLighting' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.applyLightingLocation = this.uniformLocation(gl, "applyLighting");
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
BasicTextureProgram.key = "WorldWindGpuBasicTextureProgram";
// Inherit from GpuProgram.
BasicTextureProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Loads the specified matrix as the value of this program's 'mvInverseMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
BasicTextureProgram.prototype.loadModelviewInverse = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadModelviewInverse", "missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvInverseMatrixLocation);
};
/**
* Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
BasicTextureProgram.prototype.loadModelviewProjection = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadModelviewProjection", "missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
};
/**
* Loads the specified color as the value of this program's 'color' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Color} color The color to load.
* @throws {ArgumentError} If the specified color is null or undefined.
*/
BasicTextureProgram.prototype.loadColor = function (gl, color) {
if (!color) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadColor", "missingColor"));
}
this.loadUniformColor(gl, color, this.colorLocation);
};
/**
* Loads the specified boolean as the value of this program's 'enableTexture' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Boolean} enable true to enable texturing, false to disable texturing.
*/
BasicTextureProgram.prototype.loadTextureEnabled = function (gl, enable) {
gl.uniform1i(this.textureEnabledLocation, enable ? 1 : 0);
};
/**
* Loads the specified boolean as the value of this program's 'modulateColor' uniform variable. When this
* value is true and the value of the textureEnabled variable is true, the color uniform of this shader is
* multiplied by the rounded alpha component of the texture color at each fragment. This causes the color
* to be either fully opaque or fully transparent depending on the value of the texture color's alpha value.
* This is used during picking to replace opaque or mostly opaque texture colors with the pick color, and
* to make all other texture colors transparent.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Boolean} enable true to enable modulation, false to disable modulation.
*/
BasicTextureProgram.prototype.loadModulateColor = function (gl, enable) {
gl.uniform1i(this.modulateColorLocation, enable ? 1 : 0);
};
/**
* Loads the specified number as the value of this program's 'textureSampler' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} unit The texture unit.
*/
BasicTextureProgram.prototype.loadTextureUnit = function (gl, unit) {
gl.uniform1i(this.textureUnitLocation, unit - gl.TEXTURE0);
};
/**
* Loads the specified matrix as the value of this program's 'texCoordMatrix' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The texture coordinate matrix.
*/
BasicTextureProgram.prototype.loadTextureMatrix = function (gl, matrix) {
this.loadUniformMatrix(gl, matrix, this.textureMatrixLocation);
};
/**
* Loads the specified number as the value of this program's 'opacity' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} opacity The opacity in the range [0, 1].
*/
BasicTextureProgram.prototype.loadOpacity = function (gl, opacity) {
gl.uniform1f(this.opacityLocation, opacity);
};
/**
* Loads the specified boolean as the value of this program's 'applyLighting' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} applyLighting true to apply lighting, otherwise false.
*/
BasicTextureProgram.prototype.loadApplyLighting = function (gl, applyLighting) {
gl.uniform1i(this.applyLightingLocation, applyLighting);
};
export default BasicTextureProgram;

View File

@ -0,0 +1,281 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GpuProgram
*/
import ArgumentError from '../error/ArgumentError';
import Color from '../util/Color';
import GpuShader from '../shaders/GpuShader';
import Logger from '../util/Logger';
/**
* Constructs a GPU program with specified source code for vertex and fragment shaders.
* This constructor is intended to be called only by subclasses.
* <p>
* This constructor creates WebGL shaders for the specified shader sources and attaches them to a new GLSL
* program. The method compiles the shaders and then links the program if compilation is successful. Use the
* [DrawContext.bindProgram]{@link DrawContext#bindProgram} function to make the program current during rendering.
*
* @alias GpuProgram
* @constructor
* @classdesc
* Represents an OpenGL shading language (GLSL) shader program and provides methods for identifying and
* accessing shader variables. Shader programs are created by instances of this class and made current when the
* DrawContext.bindProgram function is invoked.
* <p>
* This is an abstract class and not intended to be created directly.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {String} vertexShaderSource The source code for the vertex shader.
* @param {String} fragmentShaderSource The source code for the fragment shader.
* @param {String[]} attributeBindings An array of attribute variable names whose bindings are to be explicitly
* specified. Each name is bound to its corresponding index in the array. May be null, in which case the
* linker determines all the bindings.
* @throws {ArgumentError} If either source is null or undefined, the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
function GpuProgram(gl, vertexShaderSource, fragmentShaderSource, attributeBindings) {
if (!vertexShaderSource || !fragmentShaderSource) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
"The specified shader source is null or undefined."));
}
var program, vShader, fShader;
try {
vShader = new GpuShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
fShader = new GpuShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
} catch (e) {
if (vShader)
vShader.dispose(gl);
if (fShader)
fShader.dispose(gl);
throw e;
}
program = gl.createProgram();
if (!program) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
"Unable to create shader program."));
}
gl.attachShader(program, vShader.shaderId);
gl.attachShader(program, fShader.shaderId);
if (attributeBindings) {
for (var i = 0, len = attributeBindings.length; i < len; i++) {
gl.bindAttribLocation(program, i, attributeBindings[i]);
}
}
if (!this.link(gl, program)) {
// Get the info log before deleting the program.
var infoLog = gl.getProgramInfoLog(program);
gl.detachShader(program, vShader.shaderId);
gl.detachShader(program, fShader.shaderId);
gl.deleteProgram(program);
vShader.dispose(gl);
fShader.dispose(gl);
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
"Unable to link shader program: " + infoLog));
}
/**
* Indicates the WebGL program object associated with this GPU program.
* @type {WebGLProgram}
* @readonly
*/
this.programId = program;
// Internal. Intentionally not documented. These will be filled in as attribute locations are requested.
this.attributeLocations = {};
this.uniformLocations = {};
// Internal. Intentionally not documented.
this.vertexShader = vShader;
// Internal. Intentionally not documented.
this.fragmentShader = fShader;
// Internal. Intentionally not documented.
this.size = vertexShaderSource.length + fragmentShaderSource.length;
// Internal. Intentionally not documented.
this.scratchArray = new Float32Array(16);
}
/**
* Releases this GPU program's WebGL program and associated shaders. Upon return this GPU program's WebGL
* program ID is 0 as is that of the associated shaders.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
*/
GpuProgram.prototype.dispose = function (gl) {
if (this.programId) {
if (this.vertexShader) {
gl.detachShader(this.programId, this.vertexShader.shaderId);
}
if (this.fragmentShader) {
gl.detachShader(this.programId, this.fragmentShader.shaderId);
}
gl.deleteProgram(this.programId);
delete this.programId;
}
if (this.vertexShader) {
this.vertexShader.dispose(gl);
delete this.vertexShader;
}
if (this.fragmentShader) {
this.fragmentShader.dispose(gl);
delete this.fragmentShader;
}
this.attributeLocations = {};
this.uniformLocations = {};
};
/**
* Returns the GLSL attribute location of a specified attribute name.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {String} attributeName The name of the attribute whose location is determined.
* @returns {Number} The WebGL attribute location of the specified attribute, or -1 if the attribute is not
* found.
* @throws {ArgumentError} If the specified attribute name is null, empty or undefined.
*/
GpuProgram.prototype.attributeLocation = function (gl, attributeName) {
if (!attributeName || attributeName.length == 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "attributeLocation",
"The specified attribute name is null, undefined or empty."));
}
var location = this.attributeLocations[attributeName];
if (!location) {
location = gl.getAttribLocation(this.programId, attributeName);
this.attributeLocations[attributeName] = location;
}
return location;
};
/**
* Returns the GLSL uniform location of a specified uniform name.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {String} uniformName The name of the uniform variable whose location is determined.
* @returns {WebGLUniformLocation} The WebGL uniform location of the specified uniform variable,
* or -1 if the uniform is not found.
* @throws {ArgumentError} If the specified uniform name is null, empty or undefined.
*/
GpuProgram.prototype.uniformLocation = function (gl, uniformName) {
if (!uniformName || uniformName.length == 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "uniformLocation",
"The specified uniform name is null, undefined or empty."));
}
var location = this.uniformLocations[uniformName];
if (!location) {
location = gl.getUniformLocation(this.programId, uniformName);
this.uniformLocations[uniformName] = location;
}
return location;
};
/**
* Links a specified GLSL program. This method is not meant to be called by applications. It is called
* internally as needed.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {WebGLProgram} program The WebGL program.
* @returns {Boolean} true if linking was successful, otherwise false.
* @protected
*/
GpuProgram.prototype.link = function (gl, program) {
gl.linkProgram(program);
return gl.getProgramParameter(program, gl.LINK_STATUS);
};
/**
* Loads a specified matrix as the value of a GLSL 4x4 matrix uniform variable with the specified location.
* <p>
* This functions converts the matrix into column-major order prior to loading its components into the GLSL
* uniform variable, but does not modify the specified matrix.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
GpuProgram.prototype.loadUniformMatrix = function (gl, matrix, location) {
if (!matrix) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "loadUniformMatrix",
"missingMatrix"));
}
var columnMajorArray = matrix.columnMajorComponents(this.scratchArray);
gl.uniformMatrix4fv(location, false, columnMajorArray);
};
/**
* Loads a specified color as the value of a GLSL vec4 uniform variable with the specified location.
* <p>
* This function multiplies the red, green and blue components by the alpha component prior to loading the color
* in the GLSL uniform variable, but does not modify the specified color.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Color} color The color to load.
* @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
* @throws {ArgumentError} If the specified color is null or undefined.
*/
GpuProgram.prototype.loadUniformColor = function (gl, color, location) {
if (!color) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "loadUniformColor",
"missingColor"));
}
var premul = color.premultipliedComponents(this.scratchArray);
gl.uniform4f(location, premul[0], premul[1], premul[2], premul[3]);
};
/**
* Loads the specified RGBA color components as the value of a GLSL vec4 uniform variable with the specified
* location.
* <p>
* This function multiplies the red, green and blue components by the alpha component prior to loading the color
* in the GLSL uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} red The red component, a number between 0 and 1.
* @param {Number} green The green component, a number between 0 and 1.
* @param {Number} blue The blue component, a number between 0 and 1.
* @param {Number} alpha The alpha component, a number between 0 and 1.
* @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
*/
GpuProgram.prototype.loadUniformColorComponents = function (gl, red, green, blue, alpha, location) {
gl.uniform4f(location, red * alpha, green * alpha, blue * alpha, alpha);
};
export default GpuProgram;

View File

@ -0,0 +1,99 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GpuShader
*/
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
/**
* Constructs a GPU shader of a specified type with specified GLSL source code.
*
* @alias GpuShader
* @constructor
* @classdesc
* Represents an OpenGL shading language (GLSL) shader and provides methods for compiling and disposing
* of them.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} shaderType The type of shader, either WebGLRenderingContext.VERTEX_SHADER
* or WebGLRenderingContext.FRAGMENT_SHADER.
* @param {String} shaderSource The shader's source code.
* @throws {ArgumentError} If the shader type is unrecognized, the shader source is null or undefined or shader
* compilation fails. If the compilation fails the error thrown contains any compilation messages.
*/
function GpuShader(gl, shaderType, shaderSource) {
if (!(shaderType === gl.VERTEX_SHADER
|| shaderType === gl.FRAGMENT_SHADER)) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
"The specified shader type is unrecognized."));
}
if (!shaderSource) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
"The specified shader source is null or undefined."));
}
var shader = gl.createShader(shaderType);
if (!shader) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
"Unable to create shader of type " +
(shaderType == gl.VERTEX_SHADER ? "VERTEX_SHADER." : "FRAGMENT_SHADER.")));
}
if (!this.compile(gl, shader, shaderType, shaderSource)) {
var infoLog = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
"Unable to compile shader: " + infoLog));
}
this.shaderId = shader;
}
/**
* Compiles the source code for this shader. This method is not meant to be invoked by applications. It is
* invoked internally as needed.
* @param {WebGLRenderingContext} gl The current WebGL rendering context.
* @param {WebGLShader} shaderId The shader ID.
* @param {Number} shaderType The type of shader, either WebGLRenderingContext.VERTEX_SHADER
* or WebGLRenderingContext.FRAGMENT_SHADER.
* @param {String} shaderSource The shader's source code.
* @returns {boolean} <code>true</code> if the shader compiled successfully, otherwise <code>false</code>.
*/
GpuShader.prototype.compile = function (gl, shaderId, shaderType, shaderSource) {
gl.shaderSource(shaderId, shaderSource);
gl.compileShader(shaderId);
return gl.getShaderParameter(shaderId, gl.COMPILE_STATUS);
};
/**
* Releases this shader's WebGL shader.
* @param {WebGLRenderingContext} gl The current WebGL rendering context.
*/
GpuShader.prototype.dispose = function (gl) {
if (this.shaderId) {
gl.deleteShader(this.shaderId);
delete this.shaderId;
}
};
export default GpuShader;

View File

@ -0,0 +1,61 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports GroundProgram
*/
import AtmosphereProgram from '../shaders/AtmosphereProgram';
import GroundVertex from './glsl/ground_vertex.glsl';
import GroundFragment from './glsl/ground_fragment.glsl';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
* This method then compiles the shaders and then links the program if compilation is successful. Use the bind
* method to make the program current during rendering.
*
* @alias GroundProgram
* @constructor
* @augments AtmosphereProgram
* @classdesc GroundProgram is a GLSL program that draws the ground component of the atmosphere.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
function GroundProgram(gl) {
var vertexShaderSource = GroundVertex,
fragmentShaderSource = GroundFragment;
// Call to the superclass, which performs shader program compiling and linking.
AtmosphereProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint", "vertexTexCoord"]);
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
GroundProgram.key = "WorldWindGroundProgram";
// Inherit from AtmosphereProgram.
GroundProgram.prototype = Object.create(AtmosphereProgram.prototype);
export default GroundProgram;

View File

@ -0,0 +1,62 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports SkyProgram
*/
import AtmosphereProgram from '../shaders/AtmosphereProgram';
import SkyVertex from './glsl/sky_vertex.glsl';
import SkyFragment from './glsl/sky_fragment.glsl';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
* This method then compiles the shaders and then links the program if compilation is successful. Use the bind
* method to make the program current during rendering.
*
* @alias SkyProgram
* @constructor
* @augments AtmosphereProgram
* @classdesc SkyProgram is a GLSL program that draws the sky component of the atmosphere.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of
* the compiled shaders into a program fails.
*/
function SkyProgram(gl) {
var vertexShaderSource = SkyVertex,
fragmentShaderSource = SkyFragment;
// Call to the superclass, which performs shader program compiling and linking.
AtmosphereProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint"]);
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
SkyProgram.key = "WorldWindSkyProgram";
// Inherit from AtmosphereProgram.
SkyProgram.prototype = Object.create(AtmosphereProgram.prototype);
export default SkyProgram;

View File

@ -0,0 +1,172 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports StarFieldProgram
*/
import ArgumentError from '../error/ArgumentError';
import GpuProgram from '../shaders/GpuProgram';
import Logger from '../util/Logger';
import StarFieldVertex from './glsl/star_field_vertex.glsl';
import StarFieldFragment from './glsl/star_field_fragment.glsl';
/**
* Constructs a new program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
* This method then compiles the shaders and then links the program if compilation is successful.
* Use the bind method to make the program current during rendering.
*
* @alias StarFieldProgram
* @constructor
* @augments GpuProgram
* @classdesc StarFieldProgram is a GLSL program that draws points representing stars.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @throws {ArgumentError} If the shaders cannot be compiled, or linking of the compiled shaders into a program
* fails.
*/
function StarFieldProgram(gl) {
var vertexShaderSource = StarFieldVertex,
fragmentShaderSource = StarFieldFragment;
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint"]);
/**
* The WebGL location for this program's 'vertexPoint' attribute.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");
/**
* The WebGL location for this program's 'mvpMatrix' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");
/**
* The WebGL location for this program's 'numDays' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.numDaysLocation = this.uniformLocation(gl, "numDays");
/**
* The WebGL location for this program's 'magnitudeRangeLocation' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.magnitudeRangeLocation = this.uniformLocation(gl, "magnitudeRange");
/**
* The WebGL location for this program's 'textureSampler' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.textureUnitLocation = this.uniformLocation(gl, "textureSampler");
/**
* The WebGL location for this program's 'textureEnabled' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.textureEnabledLocation = this.uniformLocation(gl, "textureEnabled");
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
StarFieldProgram.key = "WorldWindGpuStarFieldProgram";
// Inherit from GpuProgram.
StarFieldProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
StarFieldProgram.prototype.loadModelviewProjection = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadModelviewProjection", "missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
};
/**
* Loads the specified number as the value of this program's 'numDays' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} numDays The number of days (positive or negative) since Greenwich noon, Terrestrial Time,
* on 1 January 2000 (J2000.0)
* @throws {ArgumentError} If the specified number is null or undefined.
*/
StarFieldProgram.prototype.loadNumDays = function (gl, numDays) {
if (numDays == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadNumDays", "missingNumDays"));
}
gl.uniform1f(this.numDaysLocation, numDays);
};
/**
* Loads the specified numbers as the value of this program's 'magnitudeRange' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} minMag
* @param {Number} maxMag
* @throws {ArgumentError} If the specified numbers are null or undefined.
*/
StarFieldProgram.prototype.loadMagnitudeRange = function (gl, minMag, maxMag) {
if (minMag == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadMagRange", "missingMinMag"));
}
if (maxMag == null) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadMagRange", "missingMaxMag"));
}
gl.uniform2f(this.magnitudeRangeLocation, minMag, maxMag);
};
/**
* Loads the specified number as the value of this program's 'textureSampler' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} unit The texture unit.
*/
StarFieldProgram.prototype.loadTextureUnit = function (gl, unit) {
gl.uniform1i(this.textureUnitLocation, unit - gl.TEXTURE0);
};
/**
* Loads the specified boolean as the value of this program's 'textureEnabledLocation' uniform variable.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Boolean} value
*/
StarFieldProgram.prototype.loadTextureEnabled = function (gl, value) {
gl.uniform1i(this.textureEnabledLocation, value ? 1 : 0);
};
export default StarFieldProgram;

View File

@ -0,0 +1,212 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports SurfaceTileRendererProgram
*/
import ArgumentError from '../error/ArgumentError';
import Color from '../util/Color';
import GpuProgram from '../shaders/GpuProgram';
import Logger from '../util/Logger';
import SurfaceTileVertex from './glsl/surface_tile_vertex.glsl';
import SurfaceTileFragment from './glsl/surface_tile_fragment.glsl';
/**
* Constructs a new surface-tile-renderer program.
* Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
* <p>
* This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
* method then compiles the shaders and links the program if compilation is successful. Use the bind method to make the
* program current during rendering.
*
* @alias SurfaceTileRendererProgram
* @constructor
* @augments GpuProgram
* @classdesc A GLSL program that draws textured geometry on the globe's terrain.
* Application's typically do not interact with this class.
* @param {WebGLRenderingContext} gl The current WebGL context.
*/
function SurfaceTileRendererProgram(gl) {
var vertexShaderSource = SurfaceTileVertex,
fragmentShaderSource = SurfaceTileFragment;
// Call to the superclass, which performs shader program compiling and linking.
GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource);
// Capture the attribute and uniform locations.
/**
* This program's vertex point location.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");
/**
* This program's texture coordinate location.
* @type {Number}
* @readonly
*/
this.vertexTexCoordLocation = this.attributeLocation(gl, "vertexTexCoord");
/**
* This program's modelview-projection matrix location.
* @type {WebGLUniformLocation}
* @readonly
*/
this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");
/**
* The WebGL location for this program's 'color' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.colorLocation = this.uniformLocation(gl, "color");
/**
* The WebGL location for this program's 'modulateColor' uniform.
* @type {WebGLUniformLocation}
* @readonly
*/
this.modulateColorLocation = this.uniformLocation(gl, "modulateColor");
// The rest of these are strictly internal and intentionally not documented.
this.texSamplerMatrixLocation = this.uniformLocation(gl, "texSamplerMatrix");
this.texMaskMatrixLocation = this.uniformLocation(gl, "texMaskMatrix");
this.texSamplerLocation = this.uniformLocation(gl, "texSampler");
this.opacityLocation = this.uniformLocation(gl, "opacity");
/**
* The WebGL location for this program's 'vertexTexCoord' attribute.
* @type {Number}
* @readonly
*/
this.vertexPointLocation = -1;
}
/**
* A string that uniquely identifies this program.
* @type {string}
* @readonly
*/
SurfaceTileRendererProgram.key = "WorldWindGpuSurfaceTileRenderingProgram";
SurfaceTileRendererProgram.prototype = Object.create(GpuProgram.prototype);
/**
* Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
SurfaceTileRendererProgram.prototype.loadModelviewProjection = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadModelviewProjection",
"missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
};
/**
* Loads the specified matrix as the value of this program's 'texSamplerMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
SurfaceTileRendererProgram.prototype.loadTexSamplerMatrix = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadTexSamplerMatrix",
"missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.texSamplerMatrixLocation);
};
/**
* Loads the specified matrix as the value of this program's 'texMaskMatrix' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Matrix} matrix The matrix to load.
* @throws {ArgumentError} If the specified matrix is null or undefined.
*/
SurfaceTileRendererProgram.prototype.loadTexMaskMatrix = function (gl, matrix) {
if (!matrix) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadTexMaskMatrix",
"missingMatrix"));
}
this.loadUniformMatrix(gl, matrix, this.texMaskMatrixLocation);
};
/**
* Loads the specified texture unit ID as the value of this program's 'texSampler' uniform variable.
* The specified unit ID must be one of the GL_TEXTUREi WebGL enumerations, where i ranges from 0 to
* GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} unit The unit ID to load.
*/
SurfaceTileRendererProgram.prototype.loadTexSampler = function (gl, unit) {
gl.uniform1i(this.texSamplerLocation, unit - WebGLRenderingContext.TEXTURE0);
};
/**
* Loads the specified value as the value of this program's 'opacity' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Number} opacity The opacity to load.
*/
SurfaceTileRendererProgram.prototype.loadOpacity = function (gl, opacity) {
gl.uniform1f(this.opacityLocation, opacity);
};
/**
* Loads the specified color as the value of this program's 'color' uniform variable.
*
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Color} color The color to load.
* @throws {ArgumentError} If the specified color is null or undefined.
*/
SurfaceTileRendererProgram.prototype.loadColor = function (gl, color) {
if (!color) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadColor", "missingColor"));
}
this.loadUniformColor(gl, color, this.colorLocation);
};
/**
* Loads the specified boolean as the value of this program's 'modulateColor' uniform variable. When this
* value is true the color uniform of this shader is
* multiplied by the rounded alpha component of the texture color at each fragment. This causes the color
* to be either fully opaque or fully transparent depending on the value of the texture color's alpha value.
* This is used during picking to replace opaque or mostly opaque texture colors with the pick color, and
* to make all other texture colors transparent.
* @param {WebGLRenderingContext} gl The current WebGL context.
* @param {Boolean} enable <code>true</code> to enable modulation, <code>false</code> to disable modulation.
*/
SurfaceTileRendererProgram.prototype.loadModulateColor = function (gl, enable) {
gl.uniform1i(this.modulateColorLocation, enable ? 1 : 0);
};
export default SurfaceTileRendererProgram;

View File

@ -0,0 +1,23 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump float;
uniform float opacity;
uniform vec4 color;
uniform bool enableTexture;
uniform bool modulateColor;
uniform sampler2D textureSampler;
uniform bool applyLighting;
varying vec2 texCoord;
varying vec4 normal;
void main() {
vec4 textureColor = texture2D(textureSampler, texCoord);
float ambient = 0.15;
vec4 lightDirection = vec4(0, 0, 1, 0);
if (enableTexture && !modulateColor)
gl_FragColor = textureColor * color * opacity;
else if (enableTexture && modulateColor)
gl_FragColor = color * floor(textureColor.a + 0.5);
else
gl_FragColor = color * opacity;
if (gl_FragColor.a == 0.0) {
discard;
}
if (applyLighting) {
vec4 n = normal * (gl_FrontFacing ? 1.0 : -1.0);
gl_FragColor.rgb *= clamp(ambient + dot(lightDirection, n), 0.0, 1.0);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
attribute vec4 vertexPoint;
attribute vec4 vertexTexCoord;
attribute vec4 normalVector;
uniform mat4 mvpMatrix;
uniform mat4 mvInverseMatrix;
uniform mat4 texCoordMatrix;
uniform bool applyLighting;
varying vec2 texCoord;
varying vec4 normal;
void main() {
gl_Position = mvpMatrix * vertexPoint;
texCoord = (texCoordMatrix * vertexTexCoord).st;
if (applyLighting) {
normal = mvInverseMatrix * normalVector;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
attribute vec4 vertexPoint;
uniform mat4 mvpMatrix;
void main() {
gl_Position = mvpMatrix * vertexPoint;
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump float;
precision mediump int;
const int FRAGMODE_GROUND_PRIMARY = 2;
const int FRAGMODE_GROUND_SECONDARY = 3;
const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;
uniform int fragMode;
uniform sampler2D texSampler;
varying vec3 primaryColor;
varying vec3 secondaryColor;
varying vec2 texCoord;
void main (void) {
if (fragMode == FRAGMODE_GROUND_PRIMARY) {
gl_FragColor = vec4(primaryColor, 1.0);
} else if (fragMode == FRAGMODE_GROUND_SECONDARY) {
gl_FragColor = vec4(secondaryColor, 1.0);
} else if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {
vec4 texColor = texture2D(texSampler, texCoord);
gl_FragColor = vec4(primaryColor + texColor.rgb * (1.0 - secondaryColor), 1.0);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump int;
const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;
const int SAMPLE_COUNT = 2;
const float SAMPLES = 2.0;
const float PI = 3.141592653589;
const float Kr = 0.0025;
const float Kr4PI = Kr * 4.0 * PI;
const float Km = 0.0015;
const float Km4PI = Km * 4.0 * PI;
const float ESun = 15.0;
const float KmESun = Km * ESun;
const float KrESun = Kr * ESun;
const vec3 invWavelength = vec3(5.60204474633241, 9.473284437923038, 19.643802610477206);
const float rayleighScaleDepth = 0.25;
uniform int fragMode;
uniform mat4 mvpMatrix;
uniform mat3 texCoordMatrix;
uniform vec3 vertexOrigin;
uniform vec3 eyePoint;
uniform float eyeMagnitude; /* The eye point's magnitude */
uniform float eyeMagnitude2; /* eyeMagnitude^2 */
uniform vec3 lightDirection; /* The direction vector to the light source */
uniform float atmosphereRadius; /* The outer (atmosphere) radius */
uniform float atmosphereRadius2; /* atmosphereRadius^2 */
uniform float globeRadius; /* The inner (planetary) radius */
uniform float scale; /* 1 / (atmosphereRadius - globeRadius) */
uniform float scaleDepth; /* The scale depth (i.e. the altitude at which
the atmosphere's average density is found) */
uniform float scaleOverScaleDepth; /* fScale / fScaleDepth */
attribute vec4 vertexPoint;
attribute vec2 vertexTexCoord;
varying vec3 primaryColor;
varying vec3 secondaryColor;
varying vec2 texCoord;
float scaleFunc(float cos) {
float x = 1.0 - cos;
return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}
void sampleGround() {
/* Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the
atmosphere) */
vec3 point = vertexPoint.xyz + vertexOrigin;
vec3 ray = point - eyePoint;
float far = length(ray);
ray /= far;
vec3 start;
if (eyeMagnitude < atmosphereRadius) {
start = eyePoint;
} else {
/* Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray
passing through the atmosphere) */
float B = 2.0 * dot(eyePoint, ray);
float C = eyeMagnitude2 - atmosphereRadius2;
float det = max(0.0, B*B - 4.0 * C);
float near = 0.5 * (-B - sqrt(det));
/* Calculate the ray's starting point, then calculate its scattering offset */
start = eyePoint + ray * near;
far -= near;
}
float depth = exp((globeRadius - atmosphereRadius) / scaleDepth);
float eyeAngle = dot(-ray, point) / length(point);
float lightAngle = dot(lightDirection, point) / length(point);
float eyeScale = scaleFunc(eyeAngle);
float lightScale = scaleFunc(lightAngle);
float eyeOffset = depth*eyeScale;
float temp = (lightScale + eyeScale);
/* Initialize the scattering loop variables */
float sampleLength = far / SAMPLES;
float scaledLength = sampleLength * scale;
vec3 sampleRay = ray * sampleLength;
vec3 samplePoint = start + sampleRay * 0.5;
/* Now loop through the sample rays */
vec3 frontColor = vec3(0.0, 0.0, 0.0);
vec3 attenuate = vec3(0.0, 0.0, 0.0);
for(int i=0; i<SAMPLE_COUNT; i++) {
float height = length(samplePoint);
float depth = exp(scaleOverScaleDepth * (globeRadius - height));
float scatter = depth*temp - eyeOffset;
attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));
frontColor += attenuate * (depth * scaledLength);
samplePoint += sampleRay;
}
primaryColor = frontColor * (invWavelength * KrESun + KmESun);
secondaryColor = attenuate; /* Calculate the attenuation factor for the ground */
}
void main() {
sampleGround();
/* Transform the vertex point by the modelview-projection matrix */
gl_Position = mvpMatrix * vertexPoint;
if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {
/* Transform the vertex texture coordinate by the tex coord matrix */
texCoord = (texCoordMatrix * vec3(vertexTexCoord, 1.0)).st;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
const float g = -0.95;
const float g2 = g * g;
uniform mediump vec3 lightDirection;
varying vec3 primaryColor;
varying vec3 secondaryColor;
varying vec3 direction;
void main (void) {
float cos = dot(lightDirection, direction) / length(direction);
float rayleighPhase = 0.75 * (1.0 + cos * cos);
float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cos*cos) /
pow(1.0 + g2 - 2.0*g*cos, 1.5);
const float exposure = 2.0;
vec3 color = primaryColor * rayleighPhase + secondaryColor * miePhase;
color = vec3(1.0) - exp(-exposure * color);
gl_FragColor = vec4(color, color.b);
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
precision mediump int;
const int SAMPLE_COUNT = 2;
const float SAMPLES = 2.0;
const float PI = 3.141592653589;
const float Kr = 0.0025;
const float Kr4PI = Kr * 4.0 * PI;
const float Km = 0.0015;
const float Km4PI = Km * 4.0 * PI;
const float ESun = 15.0;
const float KmESun = Km * ESun;
const float KrESun = Kr * ESun;
const vec3 invWavelength = vec3(5.60204474633241, 9.473284437923038, 19.643802610477206);
const float rayleighScaleDepth = 0.25;
uniform mat4 mvpMatrix;
uniform vec3 vertexOrigin;
uniform vec3 eyePoint;
uniform float eyeMagnitude; /* The eye point's magnitude */
uniform float eyeMagnitude2; /* eyeMagnitude^2 */
uniform mediump vec3 lightDirection; /* The direction vector to the light source */
uniform float atmosphereRadius; /* The outer (atmosphere) radius */
uniform float atmosphereRadius2; /* atmosphereRadius^2 */
uniform float globeRadius; /* The inner (planetary) radius */
uniform float scale; /* 1 / (atmosphereRadius - globeRadius) */
uniform float scaleDepth; /* The scale depth (i.e. the altitude at which the
atmosphere's average density is found) */
uniform float scaleOverScaleDepth; /* fScale / fScaleDepth */
attribute vec4 vertexPoint;
varying vec3 primaryColor;
varying vec3 secondaryColor;
varying vec3 direction;
float scaleFunc(float cos) {
float x = 1.0 - cos;
return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}
void sampleSky() {
/* Get the ray from the camera to the vertex and its length (which is the far point of
the ray passing through the atmosphere) */
vec3 point = vertexPoint.xyz + vertexOrigin;
vec3 ray = point - eyePoint;
float far = length(ray);
ray /= far;
vec3 start;
float startOffset;
if (eyeMagnitude < atmosphereRadius) {
/* Calculate the ray's starting point, then calculate its scattering offset */
start = eyePoint;
float height = length(start);
float depth = exp(scaleOverScaleDepth * (globeRadius - eyeMagnitude));
float startAngle = dot(ray, start) / height;
startOffset = depth*scaleFunc(startAngle);
} else {
/* Calculate the closest intersection of the ray with the outer atmosphere (which is the near
point of the ray passing through the atmosphere) */
float B = 2.0 * dot(eyePoint, ray);
float C = eyeMagnitude2 - atmosphereRadius2;
float det = max(0.0, B*B - 4.0 * C);
float near = 0.5 * (-B - sqrt(det));
/* Calculate the ray's starting point, then calculate its scattering offset */
start = eyePoint + ray * near;
far -= near;
float startAngle = dot(ray, start) / atmosphereRadius;
float startDepth = exp(-1.0 / scaleDepth);
startOffset = startDepth*scaleFunc(startAngle);
}
/* Initialize the scattering loop variables */
float sampleLength = far / SAMPLES;
float scaledLength = sampleLength * scale;
vec3 sampleRay = ray * sampleLength;
vec3 samplePoint = start + sampleRay * 0.5;
/* Now loop through the sample rays */
vec3 frontColor = vec3(0.0, 0.0, 0.0);
for(int i=0; i<SAMPLE_COUNT; i++) {
float height = length(samplePoint);
float depth = exp(scaleOverScaleDepth * (globeRadius - height));
float lightAngle = dot(lightDirection, samplePoint) / height;
float cameraAngle = dot(ray, samplePoint) / height;
float scatter = (startOffset + depth*(scaleFunc(lightAngle) - scaleFunc(cameraAngle)));
vec3 attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));
frontColor += attenuate * (depth * scaledLength);
samplePoint += sampleRay;
}
/* Finally, scale the Mie and Rayleigh colors and set up the varying variables for the fragment
shader */
primaryColor = frontColor * (invWavelength * KrESun);
secondaryColor = frontColor * KmESun;
direction = eyePoint - point;
}
void main() {
sampleSky();
/* Transform the vertex point by the modelview-projection matrix */
gl_Position = mvpMatrix * vertexPoint;
}

Some files were not shown because too many files have changed in this diff Show More