mirror of
https://github.com/tengge1/ShadowEditor.git
synced 2026-01-18 15:02:09 +00:00
readd WebWorldWind
This commit is contained in:
parent
5ac8c7b988
commit
cffa786337
240
package-lock.json
generated
240
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
37
web/test/WebWorldWind/index.html
Normal file
37
web/test/WebWorldWind/index.html
Normal 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>
|
||||
49
web/test/WebWorldWind/rollup.config.js
Normal file
49
web/test/WebWorldWind/rollup.config.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
408
web/test/WebWorldWind/src/BasicWorldWindowController.js
Normal file
408
web/test/WebWorldWind/src/BasicWorldWindowController.js
Normal 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;
|
||||
|
||||
7
web/test/WebWorldWind/src/README.md
Normal file
7
web/test/WebWorldWind/src/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# WebWorldWind
|
||||
|
||||
This is a project from: https://github.com/NASAWorldWind/WebWorldWind
|
||||
|
||||
# License
|
||||
|
||||
Apache
|
||||
517
web/test/WebWorldWind/src/WorldWind.js
Normal file
517
web/test/WebWorldWind/src/WorldWind.js
Normal 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;
|
||||
1521
web/test/WebWorldWind/src/WorldWindow.js
Normal file
1521
web/test/WebWorldWind/src/WorldWindow.js
Normal file
File diff suppressed because it is too large
Load Diff
120
web/test/WebWorldWind/src/WorldWindowController.js
Normal file
120
web/test/WebWorldWind/src/WorldWindowController.js
Normal 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;
|
||||
|
||||
272
web/test/WebWorldWind/src/cache/GpuResourceCache.js
vendored
Normal file
272
web/test/WebWorldWind/src/cache/GpuResourceCache.js
vendored
Normal 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;
|
||||
347
web/test/WebWorldWind/src/cache/MemoryCache.js
vendored
Normal file
347
web/test/WebWorldWind/src/cache/MemoryCache.js
vendored
Normal 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;
|
||||
56
web/test/WebWorldWind/src/cache/MemoryCacheListener.js
vendored
Normal file
56
web/test/WebWorldWind/src/cache/MemoryCacheListener.js
vendored
Normal 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;
|
||||
53
web/test/WebWorldWind/src/error/AbstractError.js
Normal file
53
web/test/WebWorldWind/src/error/AbstractError.js
Normal 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;
|
||||
|
||||
46
web/test/WebWorldWind/src/error/ArgumentError.js
Normal file
46
web/test/WebWorldWind/src/error/ArgumentError.js
Normal 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;
|
||||
46
web/test/WebWorldWind/src/error/NotYetImplementedError.js
Normal file
46
web/test/WebWorldWind/src/error/NotYetImplementedError.js
Normal 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;
|
||||
48
web/test/WebWorldWind/src/error/UnsupportedOperationError.js
Normal file
48
web/test/WebWorldWind/src/error/UnsupportedOperationError.js
Normal 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;
|
||||
216
web/test/WebWorldWind/src/geom/Angle.js
Normal file
216
web/test/WebWorldWind/src/geom/Angle.js
Normal 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;
|
||||
579
web/test/WebWorldWind/src/geom/BoundingBox.js
Normal file
579
web/test/WebWorldWind/src/geom/BoundingBox.js
Normal 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;
|
||||
315
web/test/WebWorldWind/src/geom/Frustum.js
Normal file
315
web/test/WebWorldWind/src/geom/Frustum.js
Normal 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;
|
||||
140
web/test/WebWorldWind/src/geom/Line.js
Normal file
140
web/test/WebWorldWind/src/geom/Line.js
Normal 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;
|
||||
961
web/test/WebWorldWind/src/geom/Location.js
Normal file
961
web/test/WebWorldWind/src/geom/Location.js
Normal 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;
|
||||
1908
web/test/WebWorldWind/src/geom/Matrix.js
Normal file
1908
web/test/WebWorldWind/src/geom/Matrix.js
Normal file
File diff suppressed because it is too large
Load Diff
228
web/test/WebWorldWind/src/geom/Matrix3.js
Normal file
228
web/test/WebWorldWind/src/geom/Matrix3.js
Normal 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;
|
||||
|
||||
287
web/test/WebWorldWind/src/geom/Plane.js
Normal file
287
web/test/WebWorldWind/src/geom/Plane.js
Normal 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;
|
||||
207
web/test/WebWorldWind/src/geom/Position.js
Normal file
207
web/test/WebWorldWind/src/geom/Position.js
Normal 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;
|
||||
|
||||
161
web/test/WebWorldWind/src/geom/Rectangle.js
Normal file
161
web/test/WebWorldWind/src/geom/Rectangle.js
Normal 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;
|
||||
536
web/test/WebWorldWind/src/geom/Sector.js
Normal file
536
web/test/WebWorldWind/src/geom/Sector.js
Normal 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;
|
||||
321
web/test/WebWorldWind/src/geom/Vec2.js
Normal file
321
web/test/WebWorldWind/src/geom/Vec2.js
Normal 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;
|
||||
538
web/test/WebWorldWind/src/geom/Vec3.js
Normal file
538
web/test/WebWorldWind/src/geom/Vec3.js
Normal 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;
|
||||
162
web/test/WebWorldWind/src/gesture/ClickRecognizer.js
Normal file
162
web/test/WebWorldWind/src/gesture/ClickRecognizer.js
Normal 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;
|
||||
|
||||
108
web/test/WebWorldWind/src/gesture/DragRecognizer.js
Normal file
108
web/test/WebWorldWind/src/gesture/DragRecognizer.js
Normal 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;
|
||||
|
||||
793
web/test/WebWorldWind/src/gesture/GestureRecognizer.js
Normal file
793
web/test/WebWorldWind/src/gesture/GestureRecognizer.js
Normal 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;
|
||||
|
||||
132
web/test/WebWorldWind/src/gesture/PanRecognizer.js
Normal file
132
web/test/WebWorldWind/src/gesture/PanRecognizer.js
Normal 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;
|
||||
|
||||
169
web/test/WebWorldWind/src/gesture/PinchRecognizer.js
Normal file
169
web/test/WebWorldWind/src/gesture/PinchRecognizer.js
Normal 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;
|
||||
172
web/test/WebWorldWind/src/gesture/RotationRecognizer.js
Normal file
172
web/test/WebWorldWind/src/gesture/RotationRecognizer.js
Normal 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;
|
||||
|
||||
182
web/test/WebWorldWind/src/gesture/TapRecognizer.js
Normal file
182
web/test/WebWorldWind/src/gesture/TapRecognizer.js
Normal 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;
|
||||
|
||||
120
web/test/WebWorldWind/src/gesture/TiltRecognizer.js
Normal file
120
web/test/WebWorldWind/src/gesture/TiltRecognizer.js
Normal 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;
|
||||
112
web/test/WebWorldWind/src/gesture/Touch.js
Normal file
112
web/test/WebWorldWind/src/gesture/Touch.js
Normal 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;
|
||||
115
web/test/WebWorldWind/src/globe/ArcgisElevationCoverage.js
Normal file
115
web/test/WebWorldWind/src/globe/ArcgisElevationCoverage.js
Normal 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;
|
||||
2219
web/test/WebWorldWind/src/globe/ArcgisElevationWorker.js
Normal file
2219
web/test/WebWorldWind/src/globe/ArcgisElevationWorker.js
Normal file
File diff suppressed because it is too large
Load Diff
47
web/test/WebWorldWind/src/globe/AsterV2ElevationCoverage.js
Normal file
47
web/test/WebWorldWind/src/globe/AsterV2ElevationCoverage.js
Normal 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;
|
||||
44
web/test/WebWorldWind/src/globe/EarthElevationModel.js
Normal file
44
web/test/WebWorldWind/src/globe/EarthElevationModel.js
Normal 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;
|
||||
168
web/test/WebWorldWind/src/globe/ElevationCoverage.js
Normal file
168
web/test/WebWorldWind/src/globe/ElevationCoverage.js
Normal 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;
|
||||
354
web/test/WebWorldWind/src/globe/ElevationImage.js
Normal file
354
web/test/WebWorldWind/src/globe/ElevationImage.js
Normal 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;
|
||||
417
web/test/WebWorldWind/src/globe/ElevationModel.js
Normal file
417
web/test/WebWorldWind/src/globe/ElevationModel.js
Normal 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;
|
||||
|
||||
47
web/test/WebWorldWind/src/globe/GebcoElevationCoverage.js
Normal file
47
web/test/WebWorldWind/src/globe/GebcoElevationCoverage.js
Normal 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;
|
||||
651
web/test/WebWorldWind/src/globe/Globe.js
Normal file
651
web/test/WebWorldWind/src/globe/Globe.js
Normal 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;
|
||||
|
||||
226
web/test/WebWorldWind/src/globe/Terrain.js
Normal file
226
web/test/WebWorldWind/src/globe/Terrain.js
Normal 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;
|
||||
230
web/test/WebWorldWind/src/globe/TerrainTile.js
Normal file
230
web/test/WebWorldWind/src/globe/TerrainTile.js
Normal 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;
|
||||
81
web/test/WebWorldWind/src/globe/TerrainTileList.js
Normal file
81
web/test/WebWorldWind/src/globe/TerrainTileList.js
Normal 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;
|
||||
1528
web/test/WebWorldWind/src/globe/Tessellator.js
Normal file
1528
web/test/WebWorldWind/src/globe/Tessellator.js
Normal file
File diff suppressed because it is too large
Load Diff
635
web/test/WebWorldWind/src/globe/TiledElevationCoverage.js
Normal file
635
web/test/WebWorldWind/src/globe/TiledElevationCoverage.js
Normal 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;
|
||||
49
web/test/WebWorldWind/src/globe/UsgsNedElevationCoverage.js
Normal file
49
web/test/WebWorldWind/src/globe/UsgsNedElevationCoverage.js
Normal 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;
|
||||
@ -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;
|
||||
313
web/test/WebWorldWind/src/layer/AtmosphereLayer.js
Normal file
313
web/test/WebWorldWind/src/layer/AtmosphereLayer.js
Normal 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;
|
||||
158
web/test/WebWorldWind/src/layer/Layer.js
Normal file
158
web/test/WebWorldWind/src/layer/Layer.js
Normal 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;
|
||||
67
web/test/WebWorldWind/src/layer/MercatorTiledImageLayer.js
Normal file
67
web/test/WebWorldWind/src/layer/MercatorTiledImageLayer.js
Normal 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;
|
||||
428
web/test/WebWorldWind/src/layer/StarFieldLayer.js
Normal file
428
web/test/WebWorldWind/src/layer/StarFieldLayer.js
Normal 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;
|
||||
|
||||
542
web/test/WebWorldWind/src/layer/TiledImageLayer.js
Normal file
542
web/test/WebWorldWind/src/layer/TiledImageLayer.js
Normal 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;
|
||||
30
web/test/WebWorldWind/src/layer/XYZLayer.js
Normal file
30
web/test/WebWorldWind/src/layer/XYZLayer.js
Normal 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;
|
||||
54
web/test/WebWorldWind/src/navigate/LookAtNavigator.js
Normal file
54
web/test/WebWorldWind/src/navigate/LookAtNavigator.js
Normal 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;
|
||||
53
web/test/WebWorldWind/src/navigate/Navigator.js
Normal file
53
web/test/WebWorldWind/src/navigate/Navigator.js
Normal 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;
|
||||
78
web/test/WebWorldWind/src/pick/PickedObject.js
Normal file
78
web/test/WebWorldWind/src/pick/PickedObject.js
Normal 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;
|
||||
114
web/test/WebWorldWind/src/pick/PickedObjectList.js
Normal file
114
web/test/WebWorldWind/src/pick/PickedObjectList.js
Normal 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;
|
||||
252
web/test/WebWorldWind/src/projections/GeographicProjection.js
Normal file
252
web/test/WebWorldWind/src/projections/GeographicProjection.js
Normal 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;
|
||||
311
web/test/WebWorldWind/src/projections/ProjectionWgs84.js
Normal file
311
web/test/WebWorldWind/src/projections/ProjectionWgs84.js
Normal 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;
|
||||
1567
web/test/WebWorldWind/src/render/DrawContext.js
Normal file
1567
web/test/WebWorldWind/src/render/DrawContext.js
Normal file
File diff suppressed because it is too large
Load Diff
147
web/test/WebWorldWind/src/render/FramebufferTexture.js
Normal file
147
web/test/WebWorldWind/src/render/FramebufferTexture.js
Normal 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;
|
||||
128
web/test/WebWorldWind/src/render/FramebufferTile.js
Normal file
128
web/test/WebWorldWind/src/render/FramebufferTile.js
Normal 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;
|
||||
251
web/test/WebWorldWind/src/render/FramebufferTileController.js
Normal file
251
web/test/WebWorldWind/src/render/FramebufferTileController.js
Normal 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;
|
||||
137
web/test/WebWorldWind/src/render/ImageTile.js
Normal file
137
web/test/WebWorldWind/src/render/ImageTile.js
Normal 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;
|
||||
75
web/test/WebWorldWind/src/render/OrderedRenderable.js
Normal file
75
web/test/WebWorldWind/src/render/OrderedRenderable.js
Normal 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;
|
||||
76
web/test/WebWorldWind/src/render/Renderable.js
Normal file
76
web/test/WebWorldWind/src/render/Renderable.js
Normal 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;
|
||||
128
web/test/WebWorldWind/src/render/ScreenCreditController.js
Normal file
128
web/test/WebWorldWind/src/render/ScreenCreditController.js
Normal 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;
|
||||
61
web/test/WebWorldWind/src/render/SurfaceRenderable.js
Normal file
61
web/test/WebWorldWind/src/render/SurfaceRenderable.js
Normal 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;
|
||||
72
web/test/WebWorldWind/src/render/SurfaceTile.js
Normal file
72
web/test/WebWorldWind/src/render/SurfaceTile.js
Normal 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;
|
||||
189
web/test/WebWorldWind/src/render/SurfaceTileRenderer.js
Normal file
189
web/test/WebWorldWind/src/render/SurfaceTileRenderer.js
Normal 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;
|
||||
307
web/test/WebWorldWind/src/render/TextRenderer.js
Normal file
307
web/test/WebWorldWind/src/render/TextRenderer.js
Normal 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;
|
||||
187
web/test/WebWorldWind/src/render/Texture.js
Normal file
187
web/test/WebWorldWind/src/render/Texture.js
Normal 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;
|
||||
84
web/test/WebWorldWind/src/render/TextureTile.js
Normal file
84
web/test/WebWorldWind/src/render/TextureTile.js
Normal 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;
|
||||
320
web/test/WebWorldWind/src/shaders/AtmosphereProgram.js
Normal file
320
web/test/WebWorldWind/src/shaders/AtmosphereProgram.js
Normal 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;
|
||||
127
web/test/WebWorldWind/src/shaders/BasicProgram.js
Normal file
127
web/test/WebWorldWind/src/shaders/BasicProgram.js
Normal 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;
|
||||
256
web/test/WebWorldWind/src/shaders/BasicTextureProgram.js
Normal file
256
web/test/WebWorldWind/src/shaders/BasicTextureProgram.js
Normal 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;
|
||||
281
web/test/WebWorldWind/src/shaders/GpuProgram.js
Normal file
281
web/test/WebWorldWind/src/shaders/GpuProgram.js
Normal 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;
|
||||
99
web/test/WebWorldWind/src/shaders/GpuShader.js
Normal file
99
web/test/WebWorldWind/src/shaders/GpuShader.js
Normal 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;
|
||||
61
web/test/WebWorldWind/src/shaders/GroundProgram.js
Normal file
61
web/test/WebWorldWind/src/shaders/GroundProgram.js
Normal 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;
|
||||
|
||||
|
||||
|
||||
62
web/test/WebWorldWind/src/shaders/SkyProgram.js
Normal file
62
web/test/WebWorldWind/src/shaders/SkyProgram.js
Normal 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;
|
||||
|
||||
|
||||
|
||||
|
||||
172
web/test/WebWorldWind/src/shaders/StarFieldProgram.js
Normal file
172
web/test/WebWorldWind/src/shaders/StarFieldProgram.js
Normal 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;
|
||||
212
web/test/WebWorldWind/src/shaders/SurfaceTileRendererProgram.js
Normal file
212
web/test/WebWorldWind/src/shaders/SurfaceTileRendererProgram.js
Normal 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;
|
||||
23
web/test/WebWorldWind/src/shaders/glsl/basic_fragment.glsl
Normal file
23
web/test/WebWorldWind/src/shaders/glsl/basic_fragment.glsl
Normal 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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
23
web/test/WebWorldWind/src/shaders/glsl/basic_vertex.glsl
Normal file
23
web/test/WebWorldWind/src/shaders/glsl/basic_vertex.glsl
Normal 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;
|
||||
}
|
||||
40
web/test/WebWorldWind/src/shaders/glsl/ground_fragment.glsl
Normal file
40
web/test/WebWorldWind/src/shaders/glsl/ground_fragment.glsl
Normal 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);
|
||||
}
|
||||
}
|
||||
123
web/test/WebWorldWind/src/shaders/glsl/ground_vertex.glsl
Normal file
123
web/test/WebWorldWind/src/shaders/glsl/ground_vertex.glsl
Normal 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;
|
||||
}
|
||||
}
|
||||
41
web/test/WebWorldWind/src/shaders/glsl/sky_fragment.glsl
Normal file
41
web/test/WebWorldWind/src/shaders/glsl/sky_fragment.glsl
Normal 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);
|
||||
}
|
||||
123
web/test/WebWorldWind/src/shaders/glsl/sky_vertex.glsl
Normal file
123
web/test/WebWorldWind/src/shaders/glsl/sky_vertex.glsl
Normal 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
Loading…
x
Reference in New Issue
Block a user